Going Beyond 200 OK: A Guide to Detailed HTTP Responses in Elm
This tutorial covers how to extract the headers from a successful request, get the response body from a failed request, and more.
Note: As you follow this guide, it’ll probably be helpful to have the docs for
elm/http open in another tab for reference. In all code examples, assume that the following import statement is included at the top of the file:
import Http exposing (..)
Nearly every week on the Elm Slack, I see questions like these:
When I first worked with HTTP in Elm, I was in the same boat as these folks — I couldn’t figure out how to extract certain information from an HTTP response. It turns out that due to the way
elm/http has been designed, this seemingly trivial task isn’t exactly straightforward!
While I’m pretty comfortable with
elm/http now, I’m sure many people will run into this problem when they first work with HTTP in Elm. If that’s you, hopefully this guide will help you out! I’ll explain how to use
elm/http to get detailed information from an HTTP response with full working examples at the end. Let’s get started!
Everything is (Not) Going to be 200 OK
For a simple HTTP request, we only care about the status code of the response. If it’s the magical number 200, we’ll use the response body for something useful. Otherwise, we can handle the error appropriately based on the status code.
More often than not though, we care about HTTP responses in much greater detail. Information like the headers, response URL and response body on a failed request can be very important. Maybe our request resulted in a 500 error, but our server tells us useful information in the response body? Or maybe we pass an authentication token in a header. Whatever it may be, simply knowing the status code of the response isn’t enough — we need to go beyond 200 OK!
It’s All in the Details
Let’s begin by considering a successful HTTP response, and separate the response into two parts: the response body, and everything else.
In fact, that’s exactly what
elm/http does. It calls that “everything else” the metadata, and it’s defined as follows:
With the following explanations for each field:
urlof the server that actually responded (so you can detect redirects)
statusTextdescribing what the
statusCodemeans a little
Seems great! The metadata has all the extra details we could possibly need — so what’s the problem?
Well, even though the metadata is defined, it’s not returned most of the time!
Understanding Elm’s HTTP Package
Wait, how could it not be returned? Let me explain.
elm/http, we typically use the
expect functions to interpret HTTP responses, namely
expectWhatever. There’s plenty of examples in the package’s documentation as well as The Official Elm Guide on how to use these functions.
These functions all return a
Result, containing either:
- The Http.Error type, if our request was unsuccessful.
- The response body, if our request was successful. The type of the body depends on which
expectfunction we use — for example, a
Stringif we use
Notice that the metadata is nowhere to be seen!
If we want the metadata, we need to use the functions from the Elaborate Expectations section (wow, fancy!). Specifically, the functions
expectBytesResponse. We can use these to create HTTP requests that return more detailed responses. The bad news is that there isn’t too much documentation on how to use them… which is exactly why I’m writing this guide!
First of all, The only difference between
expectBytesResponse is the way you’d like to interpret the body — is your server returning a String or a bunch of Bytes? We’ll work with
expectStringResponse, as strings are a lot more readable than bytes!
expectString, which is the simplest of the
expect functions. Here’s its type signature:
expectString : (Result Error String -> msg) -> Expect msg
Notice that in the case of a successful request, we only get a
String (the response body) — no metadata here! What we’d prefer instead is a function that has a type signature like this:
Instead of returning only the body as a
String, return both the metadata and the body as
( Metadata, String ).
We can implement this using
expectStringResponse. Let’s take a look at some more type signatures from
elm/http to better understand how to use
expectStringResponse allows us to send an HTTP request and return any
Result x a) we want. We just have to provide a function that converts a
Response and into our desired
In the case of a successful HTTP request, return
Ok ( metadata, body ) instead of only the
body! Now that we have the
Result we want, pass this function to
expectStringResponse to implement
Easy! Using this function, our HTTP requests return the response body as well as the metadata! We can access the headers and response URL from the metadata. That is, if our request was successful… what if it fails?
Creating a Custom Error Type
If the HTTP request fails,
expectString will return data of the type
Compare this with
Response (shown earlier) and you’ll find that they’re very similar…but with some minor yet impactful differences.
Notice that the
BadStatus_ case of
Response includes both the metadata and the body. That’s because if the HTTP request fails with a bad status code, it still contains a response body!
BadStatus case of
Error only contains an
Int, which is the status code of the response. The body and the rest of the metadata is discarded by
To fix this, we can create our own custom error type that includes the entire metadata along with the body in the
expectStringDetailed’s type signature to use our custom error,
DetailedError instead of
expectStringDetailed: (Result DetailedError ( Metadata, String ) -> msg) -> Expect msg
All that’s left to do is modify
convertResponseString to return our custom error. In the
BadStatus branch, we put in the complete metadata and body instead of just the status code.
Sweet! We’ve now implemented a function,
expectStringDetailed, that will include all the detailed information that we might find useful in the response, like the metadata and the error body, whether or not our HTTP request was successful!
Full Examples and a Dedicated Package
I’ve created an Ellie that shows a complete example on how to use the
expectStringDetailed function we created, from sending the request using
Http.get to displaying the detailed response in our page.
I’ve also implemented the equivalent functions
expectBytesDetailed. I had to make the following modifications:
- The respective
convertfunctions take in either a JSON or Bytes decoder as the first argument.
- The metadata and body are included in the
BadBodybranch of the
ErrorDetailedtype. This branch is entered when we try and decode JSON or Bytes and fail!
ErrorDetailedtype is now polymorphic, as the body could be either
Bytesdepending on which
expectfunction is used.
Finally, I have published a package that contains all of these functions, so you don’t have to implement them yourself every time. In addition to the functions discussed, you can also get the detailed response as a record, mock API responses, and more! Here’s an Ellie that does the exact thing as the previous example, but using my package:
More often than not, we need to go beyond 200 OK. We require more than just the response body on a successful request and the status code on a failed request. In Elm, getting these details it isn’t so straightforward — you need to use the ‘elaborate’ functions
expectBytesResponse to return a more detailed
It’s a bit tedious and unintuitive to do this all the time, which leads me to wonder if it was worth simplifying the
expect functions at the cost of this extra hassle. As I mentioned at the beginning, many people are running into the same problem! It’s definitely an interesting point of discussion…
More often than not, we need to go beyond 200 OK.
Hopefully, this guide helped you understand how to handle HTTP responses in detail with
elm/http. Again, my package jzxhuang/http-extras includes everything in this guide and more. Other great packages for working with HTTP include jinjor/elm-req (focuses on tasks) and rakutentech/http-trinity (includes an interesting way of mocking requests, which I also contributed to).
Thanks for reading, and if you have any questions or comments, post away!