Fetching JSON Data Safely in Elm with Http.get and Exhaustive Error Handling
Learn how to fetch JSON in Elm using Http.get, decode the payload, and handle both success and error cases explicitly so the compiler guarantees you never miss an error.
10 Sept 2025, 04:14 UTC

Useful answer
To fetch data from an HTTP endpoint without risking silent failures, return a Cmd Msg from init or update that wraps Http.get with a JSON decoder, and handle the resulting Result Http.Error a explicitly in your update function. The Elm compiler forces you to cover both the success and error cases, so you cannot forget to treat network problems.
How it works – a worked example
Assume we want to display a user’s name from https://api.example.com/user/1. The JSON looks like { "name": "Ada" }. We'll define a Model that holds either the name, an error message, or a loading state.
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
import Http
import Json.Decode as Decode exposing (Decoder, field, string)
-- MODEL
type alias Model =
{ name : Result String String
, loading : Bool
}
-- MSG
type Msg
= Fetch
| GotName (Result Http.Error String)
-- INIT
init : ( Model, Cmd Msg )
init =
( { name = Err "", loading = False }
, Cmd.none
)
-- UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Fetch ->
( { model | loading = True }
, Http.get
{ url = "https://api.example.com/user/1"
, expect = Http.expectString GotName
}
)
GotName result ->
let
nameResult =
result
|> Decode.decodeString (field "name" Decode.string)
|> Result.mapError (\(_)-> "Failed to decode name")
in
( { model
| name = nameResult
, loading = False
}
, Cmd.none
)
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Fetch ] [ text "Load name" ]
, div []
[ if model.loading then
text "Loading…"
else
case model.name of
Ok name ->
text ("Name: " ++ name)
Err err ->
text ("Error: " ++ err)
]
]
-- MAIN
main : Program () Model Msg
main =
Browser.sandbox { init = init, update = update, view = view }
Where to run: Save the code as src/Main.elm in a fresh Elm project (elm init), then compile with elm make src/Main.elm --output=main.js. Open the generated HTML in a browser; the console should show no Elm‑related errors.
Expected checks: After clicking “Load name”, the UI either displays the name or an error message. If the network fails or the JSON shape changes, the Err branch runs, and you see the error text instead of a silent failure.
Limits and common mistakes
- Decoder boilerplate: Each JSON shape requires a manual
Decoder. Deeply nested payloads can become verbose; consider the community packageJson.Decode.Pipelineto reduce repetition, but remember it adds a dependency. - Large single Model: As the app grows, keeping all remote data in one top‑level
Modelcan be unwieldy. Split concerns usingBrowser.elementor custom components (nested TEA) when the model exceeds a few dozen fields. - Missing error handling: If you pattern‑match only on
Okand leaveErrunhandled, the compiler will emit a non‑exhaustive match warning, preventing the mistake. Never suppress this warning with_unless you intentionally ignore errors. - Ports vs. Commands: Ports are the only way to talk to JavaScript, but they are asynchronous and limited to serializable values. Use
Cmdfor HTTP, timers, websockets, etc.; reserve ports for cases where you truly need to interact with JS libraries.
Practical verification
- Compile the project:
elm make src/Main.elm --warn. Ensure there are no warnings about missingMsgcases. - Run
elm reactoror serve the compiledmain.jswith a static server. - Open the browser console; you should see no Elm runtime exceptions.
- Toggle network (e.g., enable offline mode) and verify the error branch displays a message.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.