🧚 A practical web framework for Gleam

Further documentation

+67 -25
+8 -4
examples/5-using-a-database/src/app.gleam
··· 11 11 wisp.configure_logger() 12 12 let secret_key_base = wisp.random_string(64) 13 13 14 - // TODO: document 14 + // A database creation is created here, when the program starts. 15 + // This connection is used by all requests. 15 16 use db <- tiny_database.with_connection(data_directory) 16 17 17 - // TODO: document 18 + // A context is constructed to hold the database connection. 18 19 let context = web.Context(db: db) 19 20 20 - // TODO: document 21 + // The handle_request function is partially applied with the context to make 22 + // the request handler function that only takes a request. 23 + let handler = router.handle_request(_, context) 24 + 21 25 let assert Ok(_) = 22 - router.handle_request(_, context) 26 + handler 23 27 |> wisp.mist_handler(secret_key_base) 24 28 |> mist.new 25 29 |> mist.port(8000)
+6
examples/5-using-a-database/src/app/router.gleam
··· 5 5 pub fn handle_request(req: Request, ctx: Context) -> Response { 6 6 use req <- web.middleware(req) 7 7 8 + // A new `app/web/people` module now contains the handlers and other functions 9 + // relating to the People feature of the application. 10 + // 11 + // The router module now only deals with routing, and dispatches to the 12 + // feature modules for handling requests. 13 + // 8 14 case wisp.path_segments(req) { 9 15 ["people"] -> people.all(req, ctx) 10 16 ["people", id] -> people.one(req, ctx, id)
+7
examples/5-using-a-database/src/app/web.gleam
··· 1 1 import wisp 2 2 import tiny_database 3 3 4 + // A new Context type, which holds any additional data that the request handlers 5 + // need in addition to the request. 6 + // 7 + // Here it is holding a database connection, but it could hold anything else 8 + // such as API keys, IO performing functions (so they can be swapped out in 9 + // tests for mock implementations), configuration, and so on. 10 + // 4 11 pub type Context { 5 12 Context(db: tiny_database.Connection) 6 13 }
+45 -20
examples/5-using-a-database/src/app/web/people.gleam
··· 7 7 import tiny_database 8 8 import wisp.{Request, Response} 9 9 10 + // This request handler is used for requests to `/people`. 11 + // 10 12 pub fn all(req: Request, ctx: Context) -> Response { 13 + // Dispatch to the appropriate handler based on the HTTP method. 11 14 case req.method { 12 15 Get -> list_people(ctx) 13 16 Post -> create_person(req, ctx) ··· 15 18 } 16 19 } 17 20 21 + // This request handler is used for requests to `/people/:id`. 22 + // 18 23 pub fn one(req: Request, ctx: Context, id: String) -> Response { 24 + // Dispatch to the appropriate handler based on the HTTP method. 19 25 case req.method { 20 26 Get -> read_person(ctx, id) 21 27 _ -> wisp.method_not_allowed([Get]) ··· 26 32 Person(name: String, favourite_colour: String) 27 33 } 28 34 35 + // This handler returns a list of all the people in the database, in JSON 36 + // format. 37 + // 29 38 pub fn list_people(ctx: Context) -> Response { 30 39 let result = { 40 + // Get all the ids from the database. 31 41 use ids <- try(tiny_database.list(ctx.db)) 32 - let object = 33 - json.object([ 34 - #( 35 - "people", 36 - json.array(ids, fn(id) { json.object([#("id", json.string(id))]) }), 37 - ), 38 - ]) 39 - Ok(json.to_string_builder(object)) 42 + 43 + // Convert the ids into a JSON array of objects. 44 + Ok(json.to_string_builder(json.object([ 45 + #( 46 + "people", 47 + json.array(ids, fn(id) { json.object([#("id", json.string(id))]) }), 48 + ), 49 + ]))) 40 50 } 41 51 42 52 case result { 53 + // When everything goes well we return a 200 response with the JSON. 43 54 Ok(json) -> wisp.json_response(json, 200) 55 + 56 + // In a later example we will see how to return specific errors to the user 57 + // depending on what went wrong. For now we will just return a 500 error. 44 58 Error(Nil) -> wisp.internal_server_error() 45 59 } 46 60 } 47 61 48 62 pub fn create_person(req: Request, ctx: Context) -> Response { 63 + // Read the JSON from the request body. 49 64 use json <- wisp.require_json(req) 50 65 51 66 let result = { 67 + // Decode the JSON into a Person record. 52 68 use person <- try(decode_person(json)) 69 + 70 + // Save the person to the database. 53 71 use id <- try(save_to_database(ctx.db, person)) 54 - let object = json.object([#("id", json.string(id))]) 55 - Ok(json.to_string_builder(object)) 72 + 73 + // Construct a JSON payload with the id of the newly created person. 74 + Ok(json.to_string_builder(json.object([#("id", json.string(id))]))) 56 75 } 57 76 77 + // Return an appropriate response depending on whether everything went well or 78 + // if there was an error. 58 79 case result { 59 80 Ok(json) -> wisp.json_response(json, 201) 60 81 Error(Nil) -> wisp.unprocessable_entity() ··· 63 84 64 85 pub fn read_person(ctx: Context, id: String) -> Response { 65 86 let result = { 87 + // Read the person with the given id from the database. 66 88 use person <- try(read_from_database(ctx.db, id)) 67 - let object = 68 - json.object([ 69 - #("id", json.string(id)), 70 - #("name", json.string(person.name)), 71 - #("favourite-colour", json.string(person.favourite_colour)), 72 - ]) 73 - Ok(json.to_string_builder(object)) 89 + 90 + // Construct a JSON payload with the person's details. 91 + Ok(json.to_string_builder(json.object([ 92 + #("id", json.string(id)), 93 + #("name", json.string(person.name)), 94 + #("favourite-colour", json.string(person.favourite_colour)), 95 + ]))) 74 96 } 75 97 98 + // Return an appropriate response. 76 99 case result { 77 - Ok(json) -> wisp.json_response(json, 201) 100 + Ok(json) -> wisp.json_response(json, 200) 78 101 Error(Nil) -> wisp.not_found() 79 102 } 80 103 } ··· 94 117 |> result.nil_error 95 118 } 96 119 97 - // TODO: document 120 + /// Save a person to the database and return the id of the newly created record. 98 121 pub fn save_to_database( 99 122 db: tiny_database.Connection, 100 123 person: Person, 101 124 ) -> Result(String, Nil) { 125 + // In a real application you might use a database client with some SQL here. 126 + // Instead we create a simple map and save that. 102 127 let data = 103 128 map.from_list([ 104 129 #("name", person.name), ··· 107 132 tiny_database.insert(db, data) 108 133 } 109 134 110 - // TODO: document 111 135 pub fn read_from_database( 112 136 db: tiny_database.Connection, 113 137 id: String, 114 138 ) -> Result(Person, Nil) { 139 + // In a real application you might use a database client with some SQL here. 115 140 use data <- try(tiny_database.read(db, id)) 116 141 use name <- try(map.get(data, "name")) 117 142 use favourite_colour <- try(map.get(data, "favourite-colour"))
+1 -1
examples/5-using-a-database/test/app_test.gleam
··· 98 98 let response = router.handle_request(request, ctx) 99 99 100 100 response.status 101 - |> should.equal(201) 101 + |> should.equal(200) 102 102 103 103 response 104 104 |> testing.string_body