Dad jokes and HTTP requests in Elm
Tags: elm, webdev
Published: 2020-01-02 23:18:24

What does the Elm developer say when asked to go out to party? `Maybe`. Oh yes. Welcome to the shit show that is my dad joke game 😎

After creating both [a counter](https://dev.to/selbekk/my-first-elm-app-c70) and [a todo app](https://dev.to/selbekk/creating-a-todo-app-in-elm-i3o) in Elm, I'm ready to go for the big leagues. Create that real business value. Satisfy the client needs in a functional fashion. So I decided to create **dad joke generator** via the fantastic and free [Dad Joke API](https://icanhazdadjoke.com/api)!

Even though the feature might be laughable (hah! 😄), it's no joke to implement - at least not for me. We'll have to do HTTP calls, set the correct headers, and deal with something called commands. 

In this article, I'm going to take you through what I did step by step, and try to explain the new concepts I encounter.

## Creating a good model

I like starting with modeling the state of our application. In this case, we have three possible states - waiting for data, having failed to fetch the data, and successfully fetching the data. We can model that with a regular type:

```elm

type Model
    = Failure
    | Loading
    | Success String
```

I guess we could've created an `Idle` state as well, but we're going to fetch a joke initially anyhow, so it won't be a need for that. Speaking of...

## Initializing with commands

The next thing we want to do is intializing our application. It'll be a bit different than we've done previously - instead of just being the initial model, it'll now be a function that returns something called a tuple.

The type signature looks like this:

```elm
init : () -> (Model, Cmd Msg)
```

There's a few new things here. First, what's a tuple? In this case, you can think of it as way to return several values from a function. It's [a bit more complex than that](https://dennisreimann.de/articles/elm-data-structures-record-tuple.html), but you can think of them as a very light weight data structure of sorts.

In our case though, we're just returning two things - our initial model, and an initial `Cmd`. But what is a `Cmd`?

A `Cmd` (or command) is something you want Elm's runtime to do for you. It can be a lot of things, like creating random numbers or accessing browser APIs. Or doing HTTP calls. Once it's resolved somehow, it will return a `Msg`, which our `update` function will deal with.

> If you're coming from a React background, you can think of this `Cmd` argument to our `init` function as an action you'd want to run on mount. Kind of like `componentDidMount` or `useEffect(fn, [])` works!

So let's write our initializer!

```elm
init : () -> (Model, Cmd Msg)
init _ =
    ( Loading
    , Http.get 
        { url = "https://icanhazdadjoke.com"
        , expect = Http.expectString GotJoke 
        }
    )
```

We return a tuple with our initial model - `Loading`, and a call to the [`Http.get`](https://package.elm-lang.org/packages/elm/http/latest/Http#get) function.

> To get this to compile, you need to install the `elm/http` package with `elm install elm/http` in your terminal, and then imported in your file with `import Http`.

The `Http.get` function accepts a record with two parameters - the `url`, and something called `expect`. This last one uses another function - [`Http.expectString`](https://package.elm-lang.org/packages/elm/http/latest/Http#expectString), which tells Elm to parse the response as a string, and then "dispatch" the `GotJoke` message with the result.

But - we haven't specified our messages yet - let's do that next.

## Messages and results

For now, we only have one possible message to send in our app - namely the "we received a response from the server" value.

That "we received a response from the server" is wrapped in a `Result` type, which can either be an error or the successfully parsed response. We can specify that like so:

```elm
type Msg = 
    GotJoke (Result Http.Error String)
```

Now, I'm no functional programming nut, but if I've understood the introductory blog posts correctly, `Result` is what's known as a monad. 😱 Now, after the obligatory panic attacks, I realized monads a just containers for a value that adds some functions for you to use. However, if you're new to functional programming, just close your eyes to the monad part, and just think of `Result` as a type that's either `Ok` or `Err`. 

## Implementing the update function

The update function is a bit different this time around as well. The type signature looks like this:

```elm
update : Msg -> Model -> (Model, Cmd Msg)
```

Like before, the update function receives the message and the initial model, but now it's returning a tuple - two values - `(Model, Cmd Msg)`. This feature lets a message trigger a command, if we want to.

Let's implement it:

```elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg _ =
    case msg of
        GotJoke result ->
            case result of
                Ok joke ->
                    ( Success joke, Cmd.none )

                Err _ ->
                    ( Failure, Cmd.none )
```

Here, we pattern match the message - even though we just have a single message. By doing it this way, we'll be able to add features without refactoring too much later.

Inside of the `GotJoke` match, we're doing another pattern matching `case` to handle the two different cases of the `Result` type - an `Ok` type with the parsed result, and an `Err` type with the error (which we ignore).

Both the `Ok` and the `Err` types return a tuple with the `Msg` as the first item, and then `Cmd.none` as the second. `Cmd.none` indicates that we don't want to trigger a new command as a result of this message. This makes sense in our case - because if we got a result, we're done, and if the API call failed, there's no need to try again immediately.

## Implementing a view

I'm a front end programmer after all, so the coolest part for me is always going to be implementing the view. Here's what I put together:

```elm
view : Model -> Html Msg
view model =
    div [ A.style "text-align" "center" ]
        [ h1 [] [ text "Elm dad jokes 😎" ]
        , case model of
            Loading ->
                p [] [ text "Loading..." ]

            Failure ->
                p [ A.style "color" "red" ] [ text "Ouch, something went wrong while fetching the stuff over the network" ]

            Success theText ->
                pre [] [ text theText ]
        ]
```

Here, I create a containing `<div />`, a heading, and then pattern match our model to create three distinct views based on which state we're in.

> ### Namespacing attributes
> Notice the `A.` in `A.style`? Previously, I imported all of the possible attributes into the global namespace with `import Html.Attributes exposing (..)`. That turned out to be a bit polluting to my taste. In this app, I'm namespacing them with `A`, by changing my import to `import Html.Attributes as A`. This way, I don't pollute the global namespace as much, while still keeping the attribute names short when I need to use them.


## Putting it all together

We've forgotten one step - calling the `Browser.sandbox` function. This time, however, since we're introducing commands into the mix, we need to call a different function - `Browser.element`. It looks pretty similar, but requires another function - `subscriptions`. We're not going to bother with subscriptions this time around, so let's just say we don't have any:

```elm
subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none
```

Now, we got all the pieces to call `Browser.element`:

```elm
main =
    Browser.element
        { init = init
        , update = update
        , subscriptions = subscriptions
        , view = view
        }
```

That **should** be it! Let's run our app to get some laughs:

![A screen shot of a bunch of HTML where the joke should be](https://thepracticaldev.s3.amazonaws.com/i/jyz7jd1ics2gvr9ax0k2.png)

Argh - I must've missed something - I'm not getting the joke, I'm getting the entire HTML page! This is ridiculous, and not in the way I wanted it to be!

After reading the [API docs](https://icanhazdadjoke.com/api), you have to specify the `Accept: text/plain` HTTP header to only get the text. But how do I do that?

## Try 2 - `Http.request`

The original `Http.get` function I called earlier didn't support sending headers, but after _heading_ (hah 😄) over to the [very nice documentation](https://package.elm-lang.org/packages/elm/http/latest/Http) for the `elm/http` package, I came across another function that provided more flexibility - the `request` function.

After a bit of trial and error, I ended up with changing my `init` function to this:

```elm
init : () -> ( Model, Cmd Msg )
init _ =
    ( Loading
    , Http.request
        { method = "GET"
        , body = Http.emptyBody
        , headers = [ Http.header "Accept" "text/plain" ]
        , url = "https://icanhazdadjoke.com"
        , expect = Http.expectString GotJoke
        , timeout = Nothing
        , tracker = Nothing
        }
    )
```

A few more lines of code, but still pretty manageable. After refreshing my page, I got what I had been hoping for:

![A screen shot showing the joke "I cut my finger cutting cheese. I know it may be a cheesy story but I feel grate now."](https://thepracticaldev.s3.amazonaws.com/i/hcva3sufdzoogr0e8hle.png)

Great success!

## Fetching more jokes!

Even though that joke truly _is_ a classic, I soon wanted more. So the next feature I wanted to implement was adding a "get a new joke" button! Let's go through the steps I needed to add this new feature.

First, I added a new type to the `Msg` type:

```elm
type Msg
    = GotJoke (Result Http.Error String)
    | RequestNewJoke
```

Now my `update` function is broken, since I no longer handle all cases. Let's fix that:

```elm
update : Msg -> Model -> ( Model, Cmd Msg )
update msg _ =
    case msg of
        GotJoke result ->
            case result of
                Ok text ->
                    ( Success text, Cmd.none )

                Err _ ->
                    ( Failure, Cmd.none )

        RequestNewJoke ->
            ( Loading, fetchDadJoke )
```

If we receive a `RequestNewJoke` message, we set the state to `Loading`, and trigger the command `fetchDadJoke`. But where does that last part come from?

The `fetchDadJoke` command is refactored out from our init function, and is the code that calls the API:

```elm
fetchDadJoke : Cmd Msg
fetchDadJoke =
    Http.request
        { method = "GET"
        , body = Http.emptyBody
        , headers = [ Http.header "Accept" "text/plain" ]
        , url = "https://icanhazdadjoke.com"
        , expect = Http.expectString GotJoke
        , timeout = Nothing
        , tracker = Nothing
        }

init : () -> ( Model, Cmd Msg )
init _ =
    ( Loading
    , fetchDadJoke
    )
```

Neat, right? I guess I could've refactored out the entire tuple, but it looks pretty nice as is, too.

Finally, we just need to add some buttons to our UI. I decided to add a retry button in case of errors, as well:

```elm
view : Model -> Html Msg
view model =
    div [ A.style "text-align" "center" ]
        [ h1 [] [ text "Elm dad jokes 😎" ]
        , case model of
            Loading ->
                p [] [ text "Loading..." ]

            Failure ->
                div []
                    [ p [ A.style "color" "red" ]
                        [ text "Ouch, something went wrong while fetching the stuff over the network"
                        ]
                    , button [ Events.onClick RequestNewJoke, A.type_ "button" ]
                        [ text "Try again" ]
                    ]

            Success theText ->
                div []
                    [ pre [] [ text theText ]
                    , button [ Events.onClick RequestNewJoke, A.type_ "button" ]
                        [ text "Get another joke" ]
                    ]
        ]
```

And that's it, really! We now have "get a new joke" support as well.

## Last thoughts and next moves

I learned a lot building this project, and I hope you did as well with following along. There's quite a few new concepts here, with `Result` and `Cmd` and what have you. However, it's coming together quite nicely.

This is the third app I've ever built in Elm, and the syntax is starting to finally feel... "right". I was very skeptical at first, but I think it's growing on me.

Handling HTTP requests was a breeze, but very few APIs just return simple text strings. So next, I want to integrate with a fully fledged JSON API, so I can try my hands on decoding and encoding JSON. I'm sure it'll be a hoot!

You can see the entire code for this app in [this gist](https://gist.github.com/selbekk/2a9992378f646624818a4fc0cc51443b), and even [try it out if you want](https://elm-dad-jokes.netlify.com/).

After a bit of struggling, I even made a CodeSandbox of it:

{% codesandbox condescending-frog-8j7x6 %}

Thanks for following along! If you find any bugs, or have any questions, please feel free to ask in the comments!