Slightly more advanced guide to making HTTP calls in Elm

azurewaters
6 min readNov 17, 2022
Message icon made with craft paper
Photo by Volodymyr Hryshchenko on Unsplash

I’ve covered some basics of making HTTP calls from Elm in my previous article. These are also covered in the official guide, over here and here. This article pulls all this information together and goes into a little more depth. We’ll cover:

  1. What to expect with HTTP calls
  2. How to handle errors in HTTP calls
  3. How to track the progress of HTTP calls

What to expect with HTTP calls

As you might have seen in the guide and the package’s documentation, there are multiple ways to make HTTP calls within Elm. The get function's the simplest, requiring only an address and an argument specifying what is being expected. Let's start there.

The get function of the Http module takes a record as an argument and returns a message. We give the url field the full path of the resource (the API endpoint or the file, for instance). Simple enough. The expect field, though, says it requires to be given an Expect msg type. Looking through the Http module's documentation, we see that there are four functions that return an Expect msg. Here are their signatures:

  1. expectWhatever : (Result Error () -> msg) -> Expect msg
  2. expectString : (Result Error String -> msg) -> Expect msg
  3. expectJson : (Result Error a -> msg) -> Decoder a -> Expect msg
  4. expectBytes : (Result Error a -> msg) -> Decoder a -> Expect msg

If we have no use for what the server sends back, pick expectWhatever. Otherwise, we can pick one of the others depending on what we expect the server to send.

Note: I won’t be covering expectBytes just yet.

When we expect strings

If it’s a String we are expecting from the server, of course, pick expectString and give it a Msg to trigger when the server responds. Like so:

Notice that GotSomeText was not sent a String but a Result. This is because many things can go wrong when talking to servers. More on that later.

When we expect JSON

The server may be sending you JSON. No problem. Use expectJSON and supply it with a JSON decoder to parse the data and a message to trigger. Like so:

So, what’s happening here?

  1. On line 17, at the start of the call, we are letting Http.request know that we are expecting JSON, and that when the data comes in, it has to be parsed with the reportDecoder.
  2. Lines 36 and 37 of reportDecoder are more or less self-explanatory -- look for a field named so-and-so and turn it's value into an Elm type of such-and-such. Most of the time, you'll probably be in good stead knowing this much. However, the third line pushes out the comfort zone a tad bit more. But, because of the amazing pipe operator (|>) you can sense that it's just a two step instruction to Elm: (1) Look for a field named “uploadedOn” in the JSON and convert it's value into an int. (2) Take that int value and then send it to a function called millisToPosix of a module called Time. (Don't worry about the details right now; we are just learning to articulate the instructions we are giving to Elm. All good?)
  3. Whether the data can be decoded or not, a Result will be produced. So, we handle both, the success as well as the failure scenarios, in the FetchedAReport section (line 25) of update.

How to handle errors in HTTP calls

Of course, there are many points of failure in making http calls: the wifi might be down, the server might be on the blitz, the JSON might have changed, etc. Errors may occur. They have to be handled.

Take a look at the two messages that handle what happens after an http call again:

  1. GotSomeText (Result Error String)
  2. FetchedAReport (Result Error Report)

Both are expecting that an error might occur. That’s why they have a Result as their associated data. The Result has two variants and might take the form of Ok x or Err y. That's why we have to pattern match in the GotSomeText result branch, for instance, under update.

If everything worked as expected, great. The first branch’s code (line 3) will get executed. However, in case an error occurred, the second branch (line 5) executes. The errors that can occur are described in the documentation, under Http.Error and summarised in the code snippet below.

One point to note: if Elm has trouble parsing the JSON, it’ll result inBadBody errors. You may want to use Debug.log and Debug.toString to output those errors to the console to figure out what went wrong. Like so:

The Debug.log (line 18) outputs the badBodyError to the console. Read it to find out where the problem in the JSON is.

How to track the progress of HTTP calls

There didn’t seem to be much information on this aspect when I was learning about HTTP calls. Don’t know if it was too simple to be explained or if people didn’t use the feature enough. Either way, here’s what I learned about tracking the progress of HTTP calls.

If an HTTP call is taking a long time to complete — suppose you are downloading a large file, or uploading a lot of data — you may want to report progress to the user so that they aren’t left thinking that something broke. To do this, you will need to track the progress of your HTTP calls.

There are three steps to tracking progress of HTTP calls:

  1. Step 1: Assign an id to the call that you want to track
  2. Step 2: Let Elm know we want to know the progress of those calls
  3. Step 3: Handle the updates

Step 1: Assign an id to the call that you want to track

The Elm Runtime reports progress of HTTP calls, and you can listen for them by subscribing to them. Since there may be more than one call progressing at a given time (maybe multiple files are being downloaded), you will need to give each HTTP call an ID. You cannot do this with the standard Http.get or Http.post functions; you will have to use a custom Http.request. Here’s its format:

Source: https://package.elm-lang.org/packages/elm/http/latest/Http#request

See that tracker bit on line 8? That's where we give the call an ID.

Supposing you had two HTTP calls that you were tracking, this is how you'd subscribe to get notified of each of their progress:

Step 2: Let Elm know we want to know the progress of those calls

Now, we need to let the Elm runtime know that we are interested in receiving updates to the progress of those calls. We can do that in subscriptions, using the IDs we just used. Like so:

As you can see on lines 9 and 10, the IDs we used in Step 1 correspond to the ones in the subscriptions. Now, if the Elm runtime reports progress for the call with the ID "callOne", the GotProgressOfCallOne message is triggered and if the Elm runtime reports progress for the call with the ID "callTwo", the GotProgressOfCallTwo message is triggered.

Step 3: Handle the updates

Now, all we have to do is handle the message when it gets called, in update. Like so:

You’d have noticed progress. It's of the Http.Progress type, which contains details about the transfer, whether outgoing or incoming. Here is the Progress type:

Source: https://package.elm-lang.org/packages/elm/http/latest/Http#Progress

Conclusion

That’s it. You’re all set. You have most of what you need to work with HTTP calls in Elm.

Do you have questions? Would you like me to explain something else? Let me know in the comments.

Have fun!

If you’d like, you can support my caffeine habit with a virtual coffee!

--

--