···11+---
22+title: "The basics for a fullstack SPA in Gleam"
33+published: 2024/7/31
44+---
55+66+# The basics for a fullstack SPA in Gleam
77+88+2024/7/31
99+1010+Gleam is a functional programming language that, contrary to many other popular functional languages, features a syntax which will feel familiar to those accustomed to C-style programming languges like C#, Javascript, and Rust. This however is where a lot of similarities end for those unfamiliar with functional programming as common constructs like `for` and `while` are missing and replaced with functional recursion while `if` and `switch` have been combined into a more powerful `case`. I won't go any more indepth on the syntax of Gleam in this article but if you're interested i'd recommend checking out the [language tour](https://tour.gleam.run/) for a good starting point.
1111+1212+With that being said this article goes into the stack and libraries used to create a fully [functional fullstack application in Gleam](https://kirakira.keii.dev) however I will not go into any specific details on how the website was made as the code is [available on github](https://github.com/dinkelspiel/kirakira). I will instead explain the basics of routing, ajax requests (fetch), and effects. This article also assumes that you have gone through the tour and know the basics like creating a gleam project and installing libraries as I will not go in to that here.
1313+1414+Before we get into all the code, the entiry codebase for this blogpost is available [on github](https://github.com/dinkelspiel/basic-fullstack-gleam) if you prefer to just look at the code.
1515+1616+## The Frontend
1717+1818+### State
1919+2020+In gleam the current reigning champ for frontend development is hands down the [lustre](https://github.com/lustre-labs/lustre) framework by the amazing [Hayleigh Thompson](https://blog.hayleigh.dev/) that follows the Model-View-Update architecture.
2121+2222+> This means that the state of the application is stored in a single, immutable data structure called the model, and updated as messages are dispatched to the runtime.
2323+2424+Coming from a background of React, this felt quite daunting. I'm used to co-locating my state with the view by using the `const [value, setValue] = useState("")` syntax in React and updating it as simply as `setValue("Hello World")` and moving from this to modelling the state as types, initing the model, then defining messages was quite the ask. Lets see the difference and similarities between these two options.
2525+2626+This is a simple counter as a React component that you've probably seen 100s of times.
2727+2828+```js
2929+function Counter() {
3030+ const [value, setValue] = useState(0);
3131+3232+ return (
3333+ <div>
3434+ <button onClick={() => setValue(value + 1)}>+</button>
3535+ {value}
3636+ <button onClick={() => setValue(value - 1)}>-</button>
3737+ </div>
3838+ );
3939+}
4040+```
4141+4242+and this is the equivalent code in Lustre
4343+4444+```rs
4545+type Model =
4646+ Int
4747+4848+fn init(initial_count: Int) -> Model {
4949+ 0
5050+}
5151+5252+pub opaque type Msg {
5353+ Increment
5454+ Decrement
5555+}
5656+5757+fn update(model: Model, msg: Msg) -> Model {
5858+ case msg {
5959+ Increment -> model + 1
6060+ Decrement -> model - 1
6161+ }
6262+}
6363+6464+fn view(model: Model) -> Element(Msg) {
6565+ let count = int.to_string(model)
6666+6767+ div([], [
6868+ button([on_click(Increment)], [text("+")]),
6969+ p([], [
7070+ text(count),
7171+ ]),
7272+ button([on_click(Decrement)], [text("-")]),
7373+ ])
7474+}
7575+```
7676+7777+If we break this down then we first define the model or the "State" for our project. For the counter we only need an integer so we will alias the Model type to an Int but for any project bigger than this you'd want to define a Constructor with named variables like so
7878+7979+```rs
8080+type Model {
8181+ Model(counter: Int, username: String, /* Any other variables here */)
8282+}
8383+```
8484+8585+Then we initalize the state as 0 and define our messages and we only need two effects: Increment and Decrement. These will be what call our updates and is the way you define messages between the html and the update function which is just a simple `case` (similar to a `switch`) over the recieved message. Here we want to add 1 to our model if the `Increment` message is sent and remove 1 from our model if the `Decrement` message is sent.
8686+8787+Then we define our view which returns a Lustre element type which is a Gleam appropriated syntax meant to be similar to html. It should be completely understandeable to anyone atleast somewhat familiar to html. The standouts to what might be considered wierd are the `[]` after the `div`/`p`/`button`, the `text` function and the type in the `on_click`. We will go through them one by one.
8888+8989+First the `[]` after our elements is where we put our attributes. The code I've written doesn't include styling so they look remarkeable empty but if we were to add some classes to our `div` then it might look something like this: `div([class("container")], [])`. Then in the middle we have the function `text` this is simply because Gleam needs explicit types and the `String` type does not convert to the lustre `Element`. Therefor lustre provides the function as a way to add text to the html. Lastly the `on_click` function takes in a `Constructor` of the `Msg` type and it is how lustre handles events. This code will send the `Increment` message to the `update` function when the + button is pressed as an example. This might seem primitive but can be quite powerful when combinding it with data in the constructors so you might send a `Increment(step: 2)` instead of just an `Increment`.
9090+9191+For this small example it is obviosuly more verbose compared to the React example but where this model really starts to shine is when you want to start breaking out components for a larger project than just a counter. In React [prop drilling](https://www.freecodecamp.org/news/avoid-prop-drilling-in-react/) is a common bad practice in React codebases so much so that [entire libraries](https://github.com/pmndrs/zustand) have been made to avoid it. And if you look at some sample zustand code:
9292+9393+```js
9494+const useStore = create((set) => ({
9595+ counter: 0,
9696+ incr: () => set((state) => ({ counter: state.counter + 1 })),
9797+ decr: () => set((state) => ({ counter: state.counter - 1 })),
9898+}));
9999+100100+function Counter() {
101101+ const { counter, incr, decr } = useStore();
102102+103103+ return (
104104+ <div>
105105+ <button onClick={() => incr()}>+</button>
106106+ {counter}
107107+ <button onClick={() => decr()}>-</button>
108108+ </div>
109109+ );
110110+}
111111+```
112112+113113+You might notice that it looks remarkeably like our Lustre example. Lustre promotes a similar state management strategy out of the box allowing [scalability](https://hackernoon.com/scalability-the-lost-level-of-react-state-management) from the start. Defining the messages and models in this way means that any function in our codebase can read from the `model` as long as it is provided in the function by the parent and each function can send out any messages to be handles by a central structure, the `update`.
114114+115115+### Create our app
116116+117117+Now when we've got the state out of the way we want to create our app. Since we are going to have a backend and a frontend we want to create folder for our two projects and then init our two projects inside of there, but we'll start with the frontend.
118118+119119+```bash
120120+$ mkdir my-app # Create our folder
121121+$ cd my-app # Enter the folder
122122+$ gleam new frontend # Create our gleam app named frontend
123123+$ cd frontend # Enter the new frontend project
124124+$ gleam add lustre lustre_dev_tools # Add the lustre and lustre_dev_tools dependencies
125125+```
126126+127127+And before we do anything else we have to go into the `/frontend/gleam.toml` and add `target = "javascript"` below the name and version. It should look something like this:
128128+129129+```toml
130130+name = "frontend"
131131+version = "1.0.0"
132132+target = "javascript"
133133+```
134134+135135+Another thing that is also required is adding a `ffi.mjs` in the `/frontend/src` directory that contains which will be our way of getting the current url of our browser inside the lustre app.
136136+137137+```js
138138+export function get_route() {
139139+ return window.location.pathname;
140140+}
141141+```
142142+143143+Throughout this article you can run the frontend by using
144144+145145+```bash
146146+$ gleam run -m lustre/dev start
147147+```
148148+149149+### Routing
150150+151151+Although state management can go a long way defining different pages is neccesary for a webapp and one thing missing from both Lustre and React is a provided routing strategy. In React this is often solved by using a framework like [Next.JS](https://nextjs.org/) or a routing specific library like [React Router](https://reactrouter.com/en/main). Lustre provides a library called [Modem](https://hexdocs.pm/modem/) which is a simple client-side routing library that provides a router and some wrappers around `window.history.pushState` to allow for routing through Lustres Messages.
152152+153153+To add `modem` to our app we can run the following command in the frontend folder
154154+155155+```bash
156156+$ gleam add modem
157157+```
158158+159159+The Modem library doesn't however provide routing for when the page loads and always defaults to `/` or the default route defined in your init method. This is because Modem only interupts route change messages and the requested route does not send one. It can be implemented rather easily however and below is the simple solution present in https://kirakira.keii.dev. First we define our Route type. Everything in Gleam should be modeled using the type system and that includes our router. Lets create a simple page that has two pages, a landing page and an about page.
160160+161161+```rs
162162+import gleam/uri.{type Uri}
163163+import lustre
164164+import lustre/effect.{type Effect}
165165+import lustre/element.{type Element}
166166+import lustre/element/html.{div, text, a, form, input, button}
167167+import lustre/attribute.{href, type_}
168168+import modem
169169+170170+// This is the entrypoint for our app and wont change much
171171+pub fn main() {
172172+ lustre.application(init, update, view)
173173+ |> lustre.start("#app", Nil)
174174+}
175175+176176+// Define our route type
177177+pub type Route {
178178+ Home
179179+ About
180180+ NotFound
181181+}
182182+183183+// Include that route in our model
184184+type Model {
185185+ Model(route: Route)
186186+}
187187+188188+// Define our OnRouteChange message in our messages
189189+pub type Msg {
190190+ OnRouteChange(Route)
191191+ // In gleam we can include data in our types so here we add Route data to our OnRouteChange message
192192+}
193193+194194+// Gleam doesn't expose any functions for getting the current url so we will use the ffi functionality to import this function from javascript later. In laymans terms this makes Gleam be able to import any javascript and use it as a function.
195195+@external(javascript, "./ffi.mjs", "get_route")
196196+fn do_get_route() -> String
197197+198198+// Define our function where we get our route
199199+fn get_route() -> Route {
200200+ let uri = case do_get_route() |> uri.parse {
201201+ Ok(uri) -> uri
202202+ _ -> panic as "Invalid uri"
203203+ // The uri is coming from our javascript integration so an invalid uri should be unreachable state so we can safely panic here
204204+ }
205205+206206+ case uri.path |> uri.path_segments {
207207+ // Here we match for the route in the uri split on the slashes so / becomes [] and /about becomes ["about"] and so on
208208+ [] -> Home
209209+ ["about"] -> About
210210+ _ -> NotFound
211211+ }
212212+}
213213+214214+// Define our function for handling when the route changes
215215+fn on_url_change(uri: Uri) -> Msg {
216216+ OnRouteChange(get_route())
217217+ // When the url changes dispatch the message for when the route changes with the new route that we get from our get_route() function
218218+}
219219+220220+// Create our model initialization
221221+fn init(_) -> #(Model, Effect(Msg)) {
222222+ #(
223223+ Model(
224224+ route: get_route(),
225225+ // Here we can get the current route when the page is initialized in the browser
226226+ ),
227227+ modem.init(on_url_change),
228228+ )
229229+}
230230+231231+// Create our update method
232232+fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
233233+ case msg {
234234+ OnRouteChange(route) -> #(
235235+ Model(
236236+ ..model,
237237+ // This isn't neccesary currently but is required to keep the state between the route changes
238238+ route: route,
239239+ ),
240240+ effect.none(),
241241+ // This just tells our program to not do anything after
242242+ )
243243+ }
244244+}
245245+246246+// Now we can define our view with our html
247247+fn view(model: Model) -> Element(Msg) {
248248+ case model.route {
249249+ // Here we match the current route in the state and return different html based on what route is recieved
250250+ Home -> div([], [text("You are on the homepage")])
251251+ About -> div([], [text("You are on the about page")])
252252+ NotFound -> div([], [text("404 Not Found")])
253253+ }
254254+}
255255+```
256256+257257+That is the code neccesary for a router in client-side Gleam. You have to do some scaffolding youself but it gives you much more control. I expect we will see more abstractions come out over the following years like the currently planned work on a framework called [Pevensive](https://github.com/Pevensie/pevensie/discussions/1) which will feature ready-to-use routing among other things similar to a framework like [Laravel](https://laravel.com/).
258258+259259+### Data Fetching
260260+261261+The datafetching in Lustre is mostly done using the [lustre_http](https://hexdocs.pm/lustre_http/index.html) library. It provides a simple function for getting and posting data in lustre Effects and Messages that I've covered previously but adding a simple data fetch to our routing example above to show posts would be to add `Post` type.
262262+263263+First we run
264264+265265+```bash
266266+$ gleam add lustre_http
267267+```
268268+269269+to add `lustre_http` to our dependecies
270270+271271+```rs
272272+pub type Post {
273273+ Post(id: Int, title: String, body: String)
274274+}
275275+276276+// And then if you want to show the data in your state you could add it to your model
277277+type Model {
278278+ Model(
279279+ ..other data
280280+ posts: List(Post)
281281+ )
282282+}
283283+```
284284+285285+Then add a `fn get_posts() -> Effect(msg)` function for getting our data from the api
286286+287287+```rs
288288+import gleam/dynamic
289289+import lustre_http
290290+import gleam/int
291291+292292+fn get_posts() {
293293+ let decoder =
294294+ dynamic.list( // We want to decode a list so we use a dynamic.list here
295295+ dynamic.decode3( // And it is a list of json that looks like this {id: 1, title: "title", body: "body"} so we use a decodeN matching the number of arguments
296296+ Post, // You input the type of your data here
297297+ dynamic.field("id", dynamic.int), // Then here and for the following lines you define the field with the name and the type
298298+ dynamic.field("title", dynamic.string),
299299+ dynamic.field("body", dynamic.string),
300300+ )
301301+ )
302302+303303+ lustre_http.get( // Then you call lustre_http get
304304+ "http://localhost:8000/posts", // This will be a call to our future backend
305305+ lustre_http.expect_json(decoder, GotPosts), // Then lustre_http exposes a method to parse the resulting data as json that takes in our json decoder from earlier with the Msg that signals that the data was recieved
306306+ )
307307+}
308308+```
309309+310310+this function can then in turn be added into our `Msg` type
311311+312312+```rs
313313+pub type Msg {
314314+ ..other messages
315315+ GotPosts(Result(List(Post), lustre_http.HttpError))
316316+}
317317+318318+// And subsequently our update method
319319+fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
320320+ case msg {
321321+ ..other messages
322322+ GotPosts(posts_result) -> case posts_result {
323323+ Ok(posts) -> #(Model(..model, posts: posts), effect.none()) // Here we set the state to our current state + our new posts
324324+ Error(_) -> panic
325325+ }
326326+ }
327327+}
328328+```
329329+330330+And to call this function on page start if you are doing something like authentication that shouldn't be done via interaction but instead automatically then you can run this function in your lustre init method.
331331+332332+```rs
333333+fn init(_) -> #(Model, Effect(Msg)) {
334334+ #(
335335+ Model(
336336+ route: get_route(),
337337+ posts: [] // This is our list of posts
338338+ ),
339339+ effect.batch([
340340+ modem.init(on_url_change), // Move the modem.init here inside the new effect.batch
341341+ get_posts(),
342342+ ])
343343+ )
344344+}
345345+```
346346+347347+If we want to now show our posts in our rudamentary frontend fron the rounting section then we can just do this:
348348+349349+```rs
350350+type Route {
351351+ ..other routes
352352+ ShowPost(post_id: Int) // Add a post page that takes in a post_id
353353+}
354354+355355+fn get_route() -> Route {
356356+ let uri = case do_get_route() |> uri.parse {
357357+ Ok(uri) -> uri
358358+ _ -> panic as "Invalid uri"
359359+ }
360360+361361+ case uri.path |> uri.path_segments {
362362+ ..other routes
363363+ ["post", post_id_string] -> {
364364+ let assert Ok(post_id) = int.parse(post_id_string) // Here we parse our post_id from our url and require it to be an int. Ideally in a production application you'd do some error handling here but we only care if it's an integer.
365365+ ShowPost(post_id) // Return the route Post with our post_id
366366+ }
367367+ }
368368+}
369369+370370+fn view(model: Model) -> Element(Msg) {
371371+ case model.route {
372372+ Home -> div([], // If we are on the homepage
373373+ list.map(model.posts, fn(post) { // Loop over all posts in our model
374374+ a([href("/post/" <> int.to_string(post.id))], [ // Return a link to /post/(post_id)
375375+ text(post.title), // With the post title as the link value
376376+ ])
377377+ })
378378+ )
379379+ ShowPost(post_id) -> { // If we are on the post page with a valid post_id
380380+ let assert Ok(post) = list.find(model.posts, fn(post) { post.id == post_id }) // We find the post matching our post_id. Same as the post_id parsing but we only care if the value is valid so we don't care about error handling.
381381+382382+ div([], [ // Show our target post
383383+ text(post.title),
384384+ text(": "),
385385+ text(post.body)
386386+ ])
387387+ }
388388+ About -> div([], [text("You are on the about page")])
389389+ NotFound -> div([], [text("404 Not Found")])
390390+ }
391391+}
392392+```
393393+394394+which will show all posts from our coming backend on our frontend.
395395+396396+And now we can use our model in our view using the `model: Model` variable passed to our view function. This is the bulk of the work that is used to allow a functioning frontend in kirakira. Now if we want to create a post request that adds a post then we can do the following.
397397+398398+#### Creating a post
399399+400400+```rs
401401+import gleam/json
402402+import gleam/option.{type Option}
403403+import lustre/event
404404+405405+type Model { // Update our model
406406+ Model(
407407+ ..previous data
408408+ title: String, // Add title and body to our model. These will be the values we create our post with
409409+ body: String
410410+ )
411411+}
412412+413413+pub type MessageErrorResponse { // Add a new type for our responses that can only have a message or an error
414414+ MessageErrorResponse(message: Option(String), error: Option(String))
415415+}
416416+417417+pub type Msg { // We also update our messages
418418+ ..other messages
419419+ TitleUpdated(value: String) // Add Title and Body updated to handle the input updating in the frontend to sync it with the state of our lustre application
420420+ BodyUpdated(value: String)
421421+ RequestCreatePost // Create a message for our form to create the post
422422+ CreatePostResponded(Result(MessageErrorResponse, lustre_http.HttpError)) // Create a message for when the backend send back a result
423423+}
424424+425425+fn init(_) -> #(Model, Effect(Msg)) { // We update our init function accordingly
426426+ #(
427427+ Model(
428428+ ..previous data
429429+ title: "", // Initalize the title and body to empty string
430430+ body: ""
431431+ ),
432432+ get_posts()
433433+ )
434434+}
435435+436436+fn create_post(model: Model) {
437437+ lustre_http.post(
438438+ "http://localhost:8000/posts", // This will be a call to our future backends create post route
439439+ json.object([
440440+ #("title", json.string(model.title)),
441441+ #("body", json.string(model.body))
442442+ ]),
443443+ lustre_http.expect_json(
444444+ dynamic.decode2(
445445+ MessageErrorResponse,
446446+ dynamic.optional_field("message", dynamic.string),
447447+ dynamic.optional_field("error", dynamic.string),
448448+ ),
449449+ CreatePostResponded
450450+ )
451451+ )
452452+}
453453+454454+fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
455455+ case msg {
456456+ ..other messages
457457+ TitleUpdated(value) -> #( // If the user updates the title input
458458+ Model(..model, title: value), // Then we update the current model with the current state and we modify the title to the new value
459459+ effect.none(),
460460+ )
461461+ BodyUpdated(value) -> #( // Same with the body
462462+ Model(..model, body: value),
463463+ effect.none(),
464464+ )
465465+ RequestCreatePost -> #(model, create_post(model)) // Run the create_post function if the RequestCreatePost message was recieved from the frontend.
466466+ CreatePostResponded(response) -> #(model, get_posts()) // If the create post responded then we want to refetch our posts
467467+ }
468468+}
469469+470470+fn view(model: Model) -> Element(Msg) {
471471+ case model.route {
472472+ ..other routes
473473+ Home ->
474474+ div([], list.append([
475475+ form([event.on_submit(RequestCreatePost)], [ // If the user submits the form by clicking on the button we request gleam to create our post
476476+ text("Title"),
477477+ input([event.on_input(TitleUpdated)]), // event.on_input sends the message TitleUpdated each time the user updates the input
478478+ text("Body"),
479479+ input([event.on_input(BodyUpdated)]), // Same here but for BodyUpdated
480480+ button([type_("submit")], [
481481+ text("Create Post")
482482+ ])
483483+ ])
484484+ ],
485485+ list.map(model.posts, fn(post) { // Loop over all posts in our model
486486+ a([href("/post/" <> int.to_string(post.id))], [ // Return a link to /post/(post_id)
487487+ text(post.title), // With the post title as the link value
488488+ ])
489489+ })
490490+ )
491491+ }
492492+}
493493+```
494494+495495+## The Backend
496496+497497+As in most programming languages, interpreted or not, Gleam features an easy to use webserver similar to the likes of [ExpressJS](https://expressjs.com/) and [Go Fiber](https://gofiber.io/) offering a simple and robost way to serve data over the wire and this solution is called [Wisp](https://gleam-wisp.github.io/wisp/). Wisp includes good examples on how you would handle the basics and for most webapps they're really all you need. A backend for a lot of projects will only really need routing, database interfacing, and responding to the incomming request with json. Most of these are covered in their [examples](https://github.com/gleam-wisp/wisp/tree/main/examples) however I will go through how I utalized these in [Kirakira](https://kirakira.keii.dev).
498498+499499+### Creating our backend app
500500+501501+We will do the same spiel so enter the `/my-app` directory (or whatever you called it) and run the following commands.
502502+503503+```bash
504504+$ gleam new backend # Create our gleam app named backend
505505+$ cd backend # Enter the new frontend project
506506+$ gleam add wisp mist gleam_http gleam_erlang simplifile gleam_json cors_builder # Add the lustre and lustre_dev_tools dependencies
507507+```
508508+509509+And create a `data.json` file in `/backend` containing a `[]` to initialize our "database".
510510+511511+### Routing
512512+513513+First we have the routing, arguably the most important part of any backend. Following the routing examples from the examples link above we use the same `app.gleam` with the main method and `/app/web.gleam` as they will not change throughout the project (atleast not until a substantial part if you aren't doing anything wierd).
514514+515515+Here is the `backend.gleam` we will use
516516+517517+```rs
518518+import backend/router
519519+import gleam/erlang/process
520520+import mist
521521+import wisp
522522+523523+pub fn main() {
524524+ wisp.configure_logger()
525525+ let secret_key_base = wisp.random_string(64)
526526+527527+ let assert Ok(_) =
528528+ wisp.mist_handler(router.handle_request, secret_key_base)
529529+ |> mist.new
530530+ |> mist.port(8000)
531531+ |> mist.start_http
532532+533533+ process.sleep_forever()
534534+}
535535+```
536536+537537+and subsequent `/backend/web.gleam`
538538+539539+```rs
540540+import wisp
541541+542542+pub fn middleware(
543543+ req: wisp.Request,
544544+ handle_request: fn(wisp.Request) -> wisp.Response,
545545+) -> wisp.Response {
546546+ let req = wisp.method_override(req)
547547+ use <- wisp.log_request(req)
548548+ use <- wisp.rescue_crashes
549549+ use req <- wisp.handle_head(req)
550550+551551+ handle_request(req)
552552+}
553553+```
554554+555555+The `router.gleam` is also unchanged but we will remove the default route and add a `/posts` route.
556556+557557+```rs
558558+import wisp.{type Request, type Response}
559559+import gleam/string_builder
560560+import gleam/http.{Get, Post as WispPost}
561561+import cors_builder as cors
562562+import backend/web
563563+import gleam/result
564564+import gleam/dynamic
565565+import gleam/json
566566+import gleam/list
567567+568568+pub fn handle_request(req: Request) -> Response {
569569+ use req <- web.middleware(req)
570570+571571+ case wisp.path_segments(req) {
572572+ ["posts"] -> case req.method { // If the user requests the posts route
573573+ Get -> list_posts(req) // And the method is GET, return a list of all posts, we will create this function later
574574+ WispPost -> create_post(req) // And if the method is POST create a post, we will create this function later
575575+ _ -> wisp.method_not_allowed([Get, WispPost]) // And if its neither return an invalid method error
576576+ }
577577+ _ -> wisp.not_found() // If the route is not /posts return a 404 not found
578578+ }
579579+}
580580+```
581581+582582+### Creating our List Post Controller
583583+584584+To handle the route we will create our `controllers` just a fancy word for function that handles a request really. We will start with our `list_posts` function from before.
585585+586586+```rs
587587+type Post { // Create a type that models our post
588588+ Post(id: Int, title: String, body: String)
589589+}
590590+591591+fn list_posts(req: Request) -> Response {
592592+ // Here we will use blocks and use statements and i will explain them more in detail later
593593+594594+ let result = {
595595+ use file_data <- result.try(simplifile.read(from: "./data.json") // To avoid this post getting even *longer* i will use a file as a database. Gleam and databases is for another article. Simplifile is a standard for filesystem usage in Gleam so we use it here
596596+ |> result.replace_error("Problem reading data.json")) // Here we also replace the error with a string so it can be returned later in the error
597597+598598+ // Here we will parse our data from json to a type and then back into json to simulate this coming from a database of some sort but this could really just be a simple returning of the file_data if you wanted to if you are just doing files that map directly to the response.
599599+600600+ let posts_decoder = // Create a decoder that parses a list of posts eg. [{id: 1, title: "Post", body: "Body"}]
601601+ dynamic.list(dynamic.decode3(
602602+ Post,
603603+ dynamic.field("id", dynamic.int),
604604+ dynamic.field("title", dynamic.string),
605605+ dynamic.field("body", dynamic.string)
606606+ ))
607607+608608+ use posts <- result.try(json.decode(from: file_data, using: posts_decoder) // Take our string file_data and turn it into our Post type using our decoder
609609+ |> result.replace_error("Problem decoding file_data to posts"))
610610+611611+ Ok(json.array(posts, fn(post) { // Encode our
612612+ json.object([
613613+ #("id", json.int(post.id)),
614614+ #("title", json.string(post.title)),
615615+ #("body", json.string(post.body))
616616+ ])
617617+ }))
618618+ }
619619+620620+ case result {
621621+ Ok(json) -> wisp.json_response(json |> json.to_string_builder, 200) // Return our json posts that we turn into a string_builder as thats whats required with a code of 200 meaning OK.
622622+ Error(_) -> wisp.unprocessable_entity() // If we encounter an error we send an empty response. If this were a real application it'd probably be best to send a json_response back.
623623+ }
624624+}
625625+```
626626+627627+So what do we do here? Well we use some syntax that might be new to you. We create our result variable and then we open a block. In gleam everything is a statement so you can assign a block with a return to a variable without breaking anything out to a function. Then we have our first problem, the use statement. It has its own [page on the tour](https://tour.gleam.run/advanced-features/use/) that I recommend checking out if you want to learn more but what it effectively does here is taking the [Result](https://tour.gleam.run/data-types/results/) that is returned from a function, here `simplifile.read`. To ignore going into details we then give the result to `result.try` and this makes it so we can do `use` on it. Now we can do our use like so `use file_data <- result.try(simplifile.read(filepath))` which will put the `Ok` value of our simplifile.read into the `file_data` variable or return an `Error` from the block. This is effectively like an early return as we continue if the result is True or exit the block with the Error if it failed.
628628+629629+Then we create our decoder which should be fairly straight-forward. We use the dynamic library to decode a list of an object that has the fields id of type Int, title of type String, and body of type String into the type `List(Post)` because we marked the object to be decoded into the `Post` type. Then we do the same use magic with our json decoder to return an error if it fails or put the parsed List of Posts in our posts variable.
630630+631631+Then we return an `Ok` where we decode the `List(Post)` into json. This has to be surrounded in the `Ok` because of our `use` statements which return `Error` types and we have to comply with the `Result` returntype. And finally we do a `case` on our `result` variable that contains the `Result` type from out block, returning a json response with the status 200 if the `result` variable is `Ok` and an error if the `result` variable from our block is an `Error`.
632632+633633+### Creating our Create Post Controller
634634+635635+Now to handle our `create_post` route we do essentially the same thing.
636636+637637+```rs
638638+// Create a type for our create post request data
639639+type CreatePost {
640640+ CreatePost(title: String, body: String)
641641+}
642642+643643+fn create_post(req: Request) -> Response {
644644+ // We will use the same scaffolding as we use in the list_posts example with our result so that can go unchanged
645645+646646+ // Get the json body from our request
647647+ use body <- wisp.require_json(req)
648648+649649+ let result = {
650650+ // Create a decoder for our request data
651651+ let create_post_decoder = dynamic.decode2(
652652+ CreatePost,
653653+ dynamic.field("title", dynamic.string),
654654+ dynamic.field("body", dynamic.string),
655655+ )
656656+657657+ use parsed_request <- result.try(case create_post_decoder(body) { // Decode our body to the CreatePost type
658658+ Ok(parsed) -> Ok(parsed)
659659+ Error(_) -> Error("Invalid body recieved")
660660+ })
661661+662662+ use file_data <- result.try(simplifile.read(from: "./data.json")) // Load the posts again from the file
663663+664664+ let posts_decoder = // Create a decoder that parses a list of posts eg. [{id: 1, title: "Post", body: "Body"}]
665665+ dynamic.list(dynamic.decode3(
666666+ Post,
667667+ dynamic.field("id", dynamic.int),
668668+ dynamic.field("title", dynamic.string),
669669+ dynamic.field("body", dynamic.string)
670670+ ))
671671+672672+ use posts <- result.try(json.decode(from: file_data, using: posts_decoder)) // Take our string file_data and turn it into our Post type using our decoder
673673+674674+ // Add the new post to the old posts
675675+ let new_posts = list.append(posts, [Post(id: list.length(posts), title: parsed_request.title, body: parsed_request.body)])
676676+677677+ let new_posts_as_json = json.array(new_posts, fn(post) { // Encode our posts to json
678678+ json.object([
679679+ #("id", json.int(post.id)),
680680+ #("title", json.string(post.title)),
681681+ #("body", json.string(post.body))
682682+ ])
683683+ })
684684+685685+ let _ = new_posts_as_json // let _ = syntax just discards the value
686686+ |> json.to_string // Turn the new posts json into a string
687687+ |> simplifile.write(to: "./data.json") // And write it to our data.json file
688688+689689+ Ok("Successfully created post") // Return a success message
690690+ }
691691+692692+ case result {
693693+ Ok(message) -> wisp.json_response(json.object([#("message", json.string(message))]) |> json.to_string_builder, 200) // Return our success
694694+ Error(_) -> wisp.unprocessable_entity() // If we encounter an error we send an empty response. If this were a real application it'd probably be best to send a json_response back.
695695+ }
696696+}
697697+```
698698+699699+And that should be it for our backend. Now if you enter the frontend you should be able to create posts and read them! I'll probably write up an article about database interfacing soon(tm) but for now this is my article on how to create a simple system to post posts between a frontend and a backend in Gleam!
700700+701701+Thank you for reading, and I humbly wish you good night as it's 4:23am <3.