An introduction to the Elm programming language

Short version


Master Logo RGB

This workshop comes in two flavors:

  • a full version, in which you need to install elm and a few tools on your computer, using npm. In this version you will use the editor of your choice, and learn how to write unit tests, in addition to create an app. You will need around 3 hours to complete this version. If you don’t have enough time now, don’t worry, everything is provided in order you can finish it later.

  • a short version, in which you don’t need to install anything on your computer. You will use an online editor. This environment does not allow executing unit tests. So the last part of the full tutorial, which adds new feature in a TDD way, will be skipped. That’s why this version will be a bit quicker to complete than the full one.

This document is the material for the short version.

1. Workshop objectives

After this workshop, you should be able to create a basic Elm application.

1.1. The content of the workshop

  • practical: GUI of an image search engine

  • introduction to the language and tools

  • first steps with elm

  • basic principles of functional programming

  • the architecture of an elm application

  • client / server communication

2. Introduction to Elm

2.1. The Elm language

  • Elm is a functional language, whose compilation generates Javascript.

  • It has very strong typing

Img ulmus americana 2209

Image : User Henryhartley on en.wikipedia - license GFDL or CC-BY-SA-3.0, via Wikimedia Commons

Elm means elm, like the tree ("orme" in French), it’s not an acronym, so it’s written in lowercase.

Elm is one of the simplest functional languages available today. This translates into a limited number of concepts in the language. This simplicity is due to the will of the creator of the language to make it accessible to the greatest number of people.

The notion of type is more advanced than in Java for example. The compiler is able to infer the type of all expressions.

2.2. A front-end language

1 development in elm
2 compilation & optimization

Elm is a front-end language only (for the moment), it ultimately produces front-end javascript code.

The developer writes in elm, tests in elm.
The elm compiler compiles and optimizes the elm code into a concise and efficient javascript code.
This code is not intended to be read by a human.
The elm code is not deployed in production, only the compiled Javacript is.

2.3. How do you use it?

  • compile elm to JS

  • include in the HTML page the file resulting from the compilation

  • call the init() function


It is possible for an elm app to interact with the native JS code.

3. Editor

You will use Ellie, which is an online Elm development environement.

It is intended to be used for initiation to Elm, or to share executable elm snipets.

When you open Ellie, it contains an example app. You can execute it by clicking the compile button, and see the result on the right part of the screen.

Just clear the app in the top left part to be ready to start the workshop.

4. Hello, World!

4.1. A basic Elm application

Copy this code to the editor:

module Main exposing (main) -- 1

import Html exposing (text) -- 2

main =
    text "Hello, World!" -- 3
1 Declares the Main module, and makes public the main function
2 Imports the "text" function of the Html module.
3 The main function invokes the text + function and passes it a string as a parameter
  • Functions and their parameters are separated by spaces, not by parentheses

  • The name of the types begins with an uppercase letter.

  • The name of the functions and values starts with a lowercase letter.

  • There is no return keyword or equivalent

  • the returned value is the evaluation of the last executed line

  • The result of the main function is of the type Html, and will be reported by the elm runtime in the DOM

  • The indentation is significant: the fact that `text "Hello, World!" is indented implies that this line is part of the body of the main function.

  • The number of spaces used for indentation is free.

  • -- is the delimiter for line comments

4.2. Compile the application

Just click the Compile button

  • The result of the compilation is a single JS file, regardless of the number of elm source files.

  • We never edit this file, whose highly optimized content is not designed to be read.

In Ellie, you won’t see the resulting JS file. It will be automatically included in the html page you can see on the bottom left part of the screen.

4.3. Format the code

Use the button

format icon

to format the code.

4.4. Look at index.html

Ellie allows you to customize the Html page which will load the Elm app. Il looks like this:

    /* you can style your program here */
  <main></main> 1
    var app = Elm.Main.init({ node: document.querySelector('main') }) 2
    // you can use ports and stuff here
1 The HTML node in which the application will be displayed
2 Launching the elm application

The init function takes as a parameter the reference of the node in which the application should be displayed

If you were not using Ellie, you whould have to add something like <script src="elm.js"></script> to load the JS file resulting from the compilation.

4.5. Expected outcome

In the right part of the screen, you should see:


5. First Steps

5.1. Produce some Html

module Main exposing (main)

import Html exposing (h1, text)

view =
    h1 [] [text "elm Image Search"] -- 1

main =
    view -- 2
1 call to function h1 of the Html module
2 call the view function

NB: added code is in bold type in all code blocks.

Html has functions for each of the HTML language tags: div, p, etc.

These functions take two parameters: a list of attributes (class, onClick, etc.)
and a list of children.
In the above example h1 has no attribute and has a single child, of the text type.

5.2. Add an input field

module Main exposing (main)

import Html exposing (..)  -- 1
import Html.Attributes exposing (type_)

view =
    div []
        [ h1 [] [text "elm image search"]
        , input [ type_ "text"] []

main =
1 exposing (..) imports all functions and types of the module

To avoid a conflict with the keyword type, the "type" attribute of the HTML language is generated by a function named type_

On the other hand, there is no problem for the name of the attribute class

5.3. Expected result


6. Styles

6.1. Add a stylesheet

In the Html part of the editor, add the loading of the Bulma CSS framework

    <link rel="stylesheet" href="">
  • Bulma is a CSS framework that has the particularity of containing no JS by default, while most of alternatives are based on JQuery. However, some extensions include JS (Slider, icon selector…​).

  • It is possible to use any CSS framework with Elm (Bootstrap among others).

6.2. Add CSS classes

module Main exposing (main)

import Html exposing (..)
import Html.Attributes exposing (class, type_)

view =
    div [ class "container" ]
        [ h1 [ class "title"  ] [ text "elm image search" ]
        , input [ type_ "text", class "medium input" ] []

main =

6.3. Expected outcome


The code for this step is available here

7. Functional programming: the basics

You may want to go directly to Type annotations if you’re already familiar with functional programming basics: pure functions, high-order functions, immutability, map/filter/reduce, lambdas…​

7.1. Definition

In computer science, functional programming is a programming paradigm —a style of building the structure and elements of computer programs— that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. It is a declarative programming paradigm, which means programming is done with expressions or declarations instead of statements.

source: Wikipedia

7.2. Immutability

Everything in Elm is immutable

This means a value or function cannot be modified after its creation

{-- no keyword var, val or def
everything is immutable value, even the functions --}

myNumber = 42

increment x =
    x + 1

You never change the value of a variable, it is not possible.

For example, adding an item to a list is done by reusing the old list. The new item simply points to the first item in the old list, which is not modified.

7.3. Higher order functions

Functional languages allow the use of higher-order functions.

A function is said to be of higher order when it takes at least one function as a parameter, and/or returns a function as a result.

7.4. Pure functions

A function is said pure if it does not produce any side effect.

The result of the call to a pure function depends only on the value of its input parameters, and does not modify anything other than its return value. This is why, for example, in elm an if must be followed by an else.

This has advantages:

  • it’s easy to test

  • it’s often easy to understand

  • it’s powerful in case of multithreading (which Elm does not allow us, because it is based on JS)

All Elm functions are pure. It means:

  • they are not based on any external state

  • they do not produce any side effects

  • they must return a result

7.5. List functions

Immutability implies you can’t do a for loop in Elm : you cannot change the index value at each iteration.

Instead why you will use some functions applicable to lists, typical of functional programming:

  • map to apply a transformation to each item in the list;

  • filter to extract from the list items matching a given criteria;

  • fold (named reduce in some other languages) to build a new data structure from the list.

7.6. Lambda

Lamba = anonymous function

Example: (\x -> x + 1) [1, 2, 3]

A lambda is a function that has no name.

In the example, the lambda is applied to each element of the list, by the map function of the List module. It will add 1 to each item in the list. x is the value of the current element. The result will be a list containing [2, 3, 4].


In elm the "antislash" character is used to introduce an anonymous function.
It was chosen because, visually, it is the closest character to the Greek letter lambda.

8. Type annotations

Type annotations are very important in elm, although they are optional. In addition to documenting your code, they help the compiler to provide clear user messages.

The type annotation defines the return type of a function. It’s written just above the function name, like:

increment : Int -> Int
increment x =
    x + 1

A type annotation in Elm is:

  • the name of the function or value;

  • the colon symbol;

  • a series of types separated by arrows.

The last type is the return type.

increment : Int → Int means the function increment takes an Int as a parameter and returns an Int.

A type name in Elm always starts with an uppercase letter.

sum : Int -> Int -> Int -- takes two Int as parameters, and returns an Int
sum a b =               -- a and b are integers
    a + b

Some types are parametrized. By example:

wordCount: List String -> Int -- takes a list of strings as parameter,
                              -- and returns an Int
wordCount words =

A type maybe generic. In this case, the type name will use one or more lowercase tags. By example List a is a generic list (similar to List<Any> or List<T> in some other languages).

List.length: List a -> Int -- takes a list of any type as parameter,
                           -- and returns an Int
List.length myList =

You can chose any name for the tags, the only constraint is to start with a lowercase letter. It allows to give an indication of what this type should contain.

view: Html msg -- takes no parameter,
               -- and returns some Html which can emit any kind of message
               -- or no message at all
view =
    text "Hello, World!"

Parentheses in an annotation type identify a function. Some examples:

apply: String -> (String -> List Int) -> Bool -- takes a String
                                              -- and a function
                                              -- which converts a String
                                              -- to a list of integers,
                                              -- and returns a boolean

selectTransformation: String -> (Int -> Int -> Int) -- takes a String
                                                    -- and returns a function
                                                    -- which takes two integers,
                                                    -- and returns an integer

9. The Elm Architecture

The Elm Architecture is often refered as "TEA"

9.1. The MVU pattern


Image by Kolja Wilcke

The principle of the MVU pattern is as follows:

  • the application includes a template, a view function and an update function.

  • The view function is in charge of rendering the model (it will generate Html)

  • The view issues messages, for example to indicate that the user has modified the content of an input field or clicked on a button;

  • The update function processes these messages and updates the model accordingly

  • This pattern is called MVU : Model - View - Update

9.2. The MVU pattern by example

muv sandbox modal
  1. the model contains the state of the application (i.e. it is a store). Initially: deleteModal=False
    This model is sent to the view function by the elm runtime when the application starts.

  2. the view function uses the initial template to decide whether or not to display the modal (i.e. delete confirmation pop-up)

  3. the user clicks on the "delete" button on the html page.

  4. the view then issues a message indicating this click

  5. the runtime invokes the update function, and transmits to it the current model as well as the message sent by the view.

  6. the update function contains the logic to be applied for each message
    for DeleteClicked, the logic consists in storing in the model with the new value of the field deleteModal
    for this purpose, the update function creates a new model, which is a copy of the old one, but in which it gives the value True to the field deleteModal.

  7. the runtime invokes the view function, and passes it the model it must render.

  8. the new template contains deleteModal=True, so the view function displays the modal allowing the user to confirm the delete.

9.3. Let’s make the runtime visible!

muv sandbox runtime

9.4. Typical structure of an elm file

-- import section
import ...

-- main function
main = main

{-- Type declaration --}
type Model....

update function and sub-functions of the update --}
update = ...

{--view function and view sub-functions--}
view = ...

The MVU is implemented in the main file of the application.
Due to the declarative nature of the language, it is not a problem in elm to create longer file than in some other languages. Here the sections are separated by multi-line comments.

9.5. Implementation of the elm architecture

TEA uses Browser module

module Main exposing (main)

import Browser
import Html exposing (..)
import Html.exposing attributes (class, type_)


The Browser module exposes four functions that implement the elm architecture.

These functions depend on the type of application you want to create. The Browser module documentation details them.

9.6. Define a type for the model

All the functions of the Browser module require a model.

Our model will be a simple chain of character.

import Html.Attributes exposing (class, type_)

type alias Model =

initialModel : Model
initialModel =

alias type, as its name suggests, allows you to define a synonym for a type.


initialModel is the variable - or function, it’s the same in elm, which defines the initial value of the model (here an empty string)

Often this function is called init, but for clarity’s sake we will name it initialModel here.

9.7. Invoke Browser.sandbox

Browser.sandbox takes a record as a parameter

main =
        { init = initialModel
        , view = view
        , update = update

A record is the name in elm for a structure.

The record expected by the sandbox function must have three fields:

  • init, whose value is that of the initial model of the application

  • view, whose value is the reference of the function in charge of rendering the model visually. In our code this function is called view.

  • update, whose value is the reference of the function that updates the model. This function is usually called update.

It gives the "anchor points" of the runtime.

9.8. Define the events emitted by the view

Create a custom type

import Html.Attributes exposing (class, type_)

type Msg =
    InputChanged String

type alias Model =

The Msg type is a custom type that will define the different messages that can be sent by the view, and processed by the update function.

The declaration of a custom type lists the different constructors that can create a value of this type. For example, a Boolean type would be described by : True | False

For the moment there is only one case in which a message is sent. We will name the type constructor InputChanged, because a message will be sent when the content of the input field has been modified. As with a function or variable name, any name can be chosen. It only needs to be unique and begins with a capital letter.

Each type constructor can accept 0, 1 or more parameters. It will be useful for us to know the new content of the input field. This is why we indicate that the InputChanged constructor takes a String type value as a parameter.

The result of a call to InputChanged is the creation of a message of type Msg.

9.9. Type annotations

Add a type annotation to the main function.

main: Program () Model Msg
main =
  • Adding type annotations documents the code, and allows the compiler to display more relevant error messages.

  • The main function does not take any parameter, and returns a Program () Model Msg, since it just calls Browser sandbox.

  • The brackets in the type annotation of the main function represent an empty tuple, called unit. It is an empty value.

  • In the Browser Sandbox documentation, "msg" starting with a lowercase letter is not a type but a label. It is equivalent to a generic type (like <T> in some other languages). Ditto for model.

9.10. Detection of events on the view

The module provides functions to react to user actions.

import ...
import Html.Events exposing (onInput)


view : Model -> Html Msg -- 1
view model =
    div [ class "container" ]
        [ h1 [ class "title"  ] [ text "elm image search" ]
        , input [ type_ "text"
                , class "medium input"
                , onInput InputChanged ] []
1 Modify the type annotation to comply with Browser.sandbox requirements

Each time the content of the input field is modified, the view will issue an InputChanged message with the new value of the input field as a parameter.

Html is a type constructor, which takes a message type as a parameter.

This type of annotation can be read as "The product view function of the Html that can send messages". And not just any messages: only those described by the Msg type we have defined.

9.11. Create an update function

view =

update: Msg -> Model -> Model
update msg model =

The update function will receive in first parameter a message sent by the view.

update takes as second parameter the current model, and returns the new model in result.

The simplest possible implementation for update is to return the current model, without modifying it. That is what we are doing.


When we say that the function update updates the model, it is for convenience, as the model is immutable. In reality, this function produces a new model, possibly from the old one, every time it is invoked.

9.12. Update the model

update must update the template according to the content of the message. The latter is analyzed using pattern matching:

update: Msg -> Model -> Model
update msg model =
    case msg of
        InputChanged value ->

If the message is of the InputChanged type, the new template will be equal to the value conveyed by the message (i.e. the content of the input field).

Pattern matching (or "pattern filtering") is a control structure which allows you to choose one of several behaviors, by looking at how the message was constructed. It will also allow to deconstruct the content of the message (unboxing) to recover its value.

In our example, msg can only have been built by calling "InputChanged" followed by a String for now.

9.13. Time-Travelling Debugger

Enter several characters in the input field, then click the DEBUG button on the top right to access the debugger. The OUTPUT button hides the debugger.

In the panel that appears, you can see each message that has been sent.

Clicking on a message returns the application to the state it was in when it was sent. In the right part we see the state of the model after processing the message by the update function.


The history can be exported and then imported on another workstation. This makes it possible, for example, to replay and analyze a scenario that caused a bug on a colleague’s workstation.

Click on resume to resume the execution of the application, otherwise it will be frozen.

9.14. Let’s summarize: the elm architecture

tea sandbox

The elm architecture is the structure of applications in this language. It is integrated into the language from the beginning and therefore eliminates the need for a framework.

  1. the browser emits an event (following an action by the user for example)

  2. the runtime, which is subscribed to this event, invokes the update function and passes it the current model + a message describing the event

  3. the update function generates a new model

  4. the runtime invokes the view function and passes it the new model

  5. view generates a new virtual DOM and sends it back to the runtime

  6. the runtime updates the real DOM

The code for this step is available here

10. Communicate with a server

10.1. Side effects

  • Side effects are managed by the runtime

  • Side effects are invoked via commands

  • The result of a side effect is provided via a message

We can only write pure functions in elm. The result of calling a function with given parameter values will ALWAYS return the same value - which is convenient for testing.

But what if you have to do an operation with a different result? For example, generate a random number? Or make an http request?

In this case we will have to issue a command that will ask the elm runtime to execute the impure function. The runtime will provide the result of executing this impure function by invoking the update function. The message may contain, for example, the random number generated.

10.2. Elm architecture with side effects

tea element

Compared to the simplest version of the elm architecture, we will have additional notions to manage the processing that causes side effects.

  1. the update function generates a new model, and possibly a command

  2. the runtime executes the command. This is where all side effects are processed (HTTP request, random number generation, etc.). The commands are asynchronous.

  3. when the execution of a command ends, the runtime invokes the update function, and passes the current model a message that describes the result of the execution of the command (success or failure, value, etc…​) ⇒ return to step 1

10.3. Change application type

Replace Browser.sandbox with Browser.element and compile

main =
        { init = init
        , view = view
        , update = update

Browser.element, unlike Browser.sandbox, allows you to interact with the world outside the application. Browser.element takes a record as a parameter with an additional field, subscriptions. The compiler clearly indicates which field is missing.

10.4. Implement subscriptions

Provide a minimum implementation of subscriptions to Browser.element :

subscriptions : Model -> Sub Msg
subscriptions model =

main =
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions

subscriptions is a function that allows you to subscribe to function calls from the JS or elm runtime, like the ticks of a clock. In the immediate future, we will provide a subscription implementation that does nothing. It will therefore return Sub.none, regardless of the value of the model passed to it as a parameter.

10.5. Adapt the initialization function

Replace initialModel by a function named init:

init : () -> (Model, Cmd Msg) -- 1
init _ = -- 2
    ("", Cmd.none) -- 3
main =
        { init = init
1 () = Unit = empty tuple = no value
2 _ = unused parameter
3 tuple (initial model, no command)

The compiler reports that the functions expected by Browser.element have signatures different from those used by Browser.sandbox
init and update must return a tuple with a Model record and a command.

The initialization function must also accept a flag as the first parameter. Flags are values that can be transmitted by the JS code when initializing the elm application (by adding a flags property to the object passed as a parameter to init())

For now, we don’t need a flag, so it will be initialized to Unit. Unit is just an empty tuple. It is used to represent an empty value.

10.6. Adapt the update function

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
    case msg of
        InputChanged value ->
            (value, Cmd.none)

As with the init function, update must now return a tuple, whose first element is the new value of the model, the second is the command to execute. Cmd.none tells the elm runtime that there is no command to execute.

In the rest of the practical we will introduce a command that will launch an HTTP request.

10.7. Add a form

The form tag around the input field is used to detect the validation.

    div [ class "container" ]
        [ h1 [ class "title" ] [ text "elm image search" ]
        , viewForm

viewForm : Html Msg
viewForm =
    form []
        [ input
            [ type_ "text"
            , class "medium input"
            , onInput InputChanged

To maintain the readability of the view function,
the code that generates the form is extracted into a new
function named viewForm.

10.8. Declare a new message constructor

Add a message constructor to be used when the form is subbmitted:

type Msg
    = InputChanged String
    | FormSubmitted

The compiler will help you for the next steps.

A message can now be either an InputChanged or a FormSubmitted.
Gradually the possible types for a message will get richer.
They will represent all possible actions on the page.

The different message constructors do not necessarily have the same types of parameters, or even have no parameters as is the case for FormSubmitted.

10.9. Detect the validation of the form

  1. import the onSubmit function of the module

  2. send a message FormSubmitted when the form is validated

  3. make sure that the application compiles

10.10. Perform an HTTP GET request

Install the elm/http module:


package icon

to display the package panel, then search for the http module and install it. Click the same button again to hide the package panel.

The documentation of the Http module gives the type annotation of the get function:

get :
    { url : String
    , expect : Expect msg
    -> Cmd msg

10.11. Send an image search request

For now we will treat the answers as text.

FormSubmitted ->
  let -- 1
    httpCommand = Http.get
      { url =
         ++ "/search/photos?query="
         ++ model
      , expect = Http.expectString ResponseReceived -- 2
  ( model, httpCommand )
1 declaration of local variables
2 expectString indicates the type of response expected

let …​ in allows you to declare local variables usable in the return value of the function.

ResponseReceived is the message that will be sent following the reception of the response. We will define this type constructor in a further step.


Here again it is good to let yourself be guided by the compiler.

10.12. Digression: the Result type

The Result type represents a response to an operation that may fail. It has two constructors: one to encapsulate the result of a successful operation, one to encapsulate an error.

type Result error value
    = Ok value
    | Err error

is equivalent to:

type Result a b
    = Ok b
    | Err a

Remember, lowercase names are just tags, not types.

10.13. Declare the type of the response

The update function must process the responses to the HTTP request.

type Msg
    = InputChanged String
    | FormSubmitted
    | ResponseReceived (Result Http.Error String)

The HTTP module documentation tells us how it is used and the format of the response messages.

The message sent by the runtime when receiving a response to an HTTP request includes a Result type parameter. This type is itself configured. The first parameter of the type Result indicates the type that the result will have in case of failure (here Http.Error). The second parameter indicates the type of the result in case of a successful query (here a string).

(Result Http.Error String) is in brackets because it is the set that forms a type. Without brackets, this would mean that the ResponseReceived type has three parameters, of the respective types Result, Http.Error and String -which is not consistent with the operation of the Http module. Thanks to the parentheses, we indicate that ResponseReceived has only one parameter, of type Result Http.Error String.


Continue to be guided by the compiler.

10.14. Process the answer

The compiler tells us that a case is not processed by the update function:

    case msg of
        InputChanged value ->
        FormSubmitted ->
        ResponseReceived _ -> -- 1
            (model, Cmd.none)
1 For the moment, we don’t know the answer

It is necessary to add a branch to the box to process the new case.

The compiler requires that all cases be processed. There can be no forgetting. It is thanks to this type of principle that an elm application never causes an error at runtime.

In the code above, the response conveyed by the response message is ignored. The model is therefore returned unchanged.


The compilation must be successful.

10.15. Enrich the model

To display the server response, the model must contain the value entered by the user and the server response.

type alias Model =
    { searchTerms : String
    , response : String

Changing the model type causes compilation errors. The compiler then tells us what changes to make to adapt the rest of the code. It is a typical elm experience. We are talking about Compiler Driven Development.

In the record type declaration the symbol : is used to separate the keys from the types (as in all type declarations in elm).

The searchTerms field will be used to store the character string entered by the user.

10.16. Initialize the new model

The initial value of the record fields is an empty string
There is no null value in elm.

init : () -> ( Model, Cmd Msg )
init _ =
    ( { searchTerms = ""
      , response = ""
    , Cmd.none

In the record initialization the symbol = is used to separate the keys from the values.

10.17. Use the record in the update function

InputChanged value ->
    ( { model | searchTerms = value }, Cmd.none ) -- 1
FormSubmitted ->
    httpCommand =
       { url =
                ++ "/search/photos?query="
                ++ model.searchTerms -- 2
       , expect = Http.expectString ResponseReceived
    ( model, httpCommand )
1 model update
2 access to the value of the searchTerms field

When we receive an InputChanged message, we will update the searchTerms field of the model to store the value entered by the user.

More precisely, as in elm everything is immutable, we will create a new model model, based on the old one, in which the searchTerms field will have a value equal to that of the value parameter of the InputChanged message.


For convenience, we say that we update the model, but from a theoretical point of view, it is an abuse of language.

10.18. Update the model based on the response received

    case msg of
        InputChanged value ->
        FormSubmitted ->

        ResponseReceived (Ok jsonString) ->
            ( ..., Cmd.none ) -- 1

        ResponseReceived (Err _) ->
            ( ..., Cmd.none ) -- 2
1 Store the received value in the response field of the model
2 Store an error message in the response field of the model

A Result object can be of the type OK or of the type Err.

If it is of type OK, its parameter will be the body of the Http response returned by the server.

If it is of type Err, its parameter will be an Http error, which should be analyzed with pattern matching to display a specific error message.

For the time being, we will store a generic error message, hard-coded in the response field of the model.


You can verify the request being made and its response using the developer tools of your Web Browser

You can also use the Elm debugger to verify the messages processed by the update function and the resulting model.

10.19. Display the answer

  1. create a viewResponse function

  2. viewResponse takes the model as a parameter and returns Html

  3. use viewResponse to display the server response or error message under the input field

Temporarily remove the query parameter from the request to cause an error 400, or disconnect your computer from the network.

10.20. Expected result




The code for this step is available here

11. Decoding JSON

11.1. Function Application Operator

To convert the Json to elm data structures, we will need the operator |> :

  • <| means: evaluate the expression on the right, pass its value to the left

  • |> means: evaluate the expression on the left, pass its value to the right

viewUsername : User -> Html Msg
viewUsername user =
    div [] [ text <| user.firstname ++ " " ++ ] -- 1
1 is equivalent to div [] [text (user.firstname " "]

These operators allow a very elegant syntax for chaining calls (ex.: apply map then filter then sort to a list)

11.2. Response from the server

The server response is in the form:

    "results" : [
            "urls" :
                "thumb" : "http://...",
                "regular" : "http://...",

The API response to an image search request is a JSON object that has a results property. The value of results is an array of objects describing images. Each of these objects has a url property, which itself has thumb and regular properties that correspond to the image sizes we are interested in.

11.3. Declare the type to be received

  1. install the elm/json package

  2. install the package NoRedInk/elm-json-decode-pipeline

  3. Add this code:

import Json.Decode as Decode exposing (Decoder, field, int, list, string)
import Json.Decode.Pipeline exposing (required, requiredAt)

type alias Image =
    { thumbnailUrl : String
    , url : String

imageDecoder : Decoder Image -- 1
imageDecoder =
    Decode.succeed Image
        |> requiredAt [ "urls", "thumb" ] string -- 2
        |> requiredAt [ "urls", "regular" ] string -- 3

imageListDecoder : Decoder (List Image) -- 4
imageListDecoder =
    field "results" (list imageDecoder)
1 imageDecoder converts the JSON received from the server to an Image type
2 the content of urls.thumb will go in the 1st field of the record
3 the content of urls.regular will go in the 2nd field of the record
4 imageListDecoder transforms the "results" field of the JSON into a list of Image records,
each of which is created using imageDecoder.
  • The first parameter of the requiredAt function is the path of the JSON property to be retrieved;

  • the second parameter, string, is a function that converts the property value into a String value. Note it’s a lowercase name, so it’s not the String type.

It is essential in the decoder declaration to respect the order of the fields in the Image type.

11.4. Change the type of message received

type Msg
    = InputChanged String
    | FormSubmitted
    | ResponseReceived (Result Http.Error (List Image))

The answer will be of Image list type and no longer of String type

11.5. Use the decoder

{ url =
            ++ "/search/photos?query="
            ++ model.searchTerms
, expect = Http.expectJson ResponseReceived imageListDecoder

expectJson takes one more parameter than expectString: the decoder to use to convert the JSON response into an elm data structure.

11.6. Storing images in the model

  1. Delete the response field of type Model.

  2. Comment or delete the response display code

  3. Add an images field to the Model type

  4. Store the server response in case of success

  5. Add a message field to the Model type

  6. Store the error message when necessary

  7. Display this message, if any, under the input field

  8. Simulate a network problem to test the display of the error message

  9. Check the correct functioning of the image list part with the debugger


If there is no error, use text "" to replace the message. It won’t add anything to the DOM.

If you want to use the not equal operator:

if a /= b then

The code for this step is available here

12. Displaying responses

12.1. Show thumbnails

view model =
    div [ class "container" ]
        [ h1 [ class "title" ] [ text "elm image search" ]
        , viewForm
        , viewMessage model
        , viewResults model

viewResults : Model -> Html Msg
viewResults model =
    div [] ( viewThumbnail model.images)

viewThumbnail : Image -> Html Msg
viewThumbnail image =
    img [ src image.thumbnailUrl ] [] applies the viewImage function to each of the elements in the model.images list, and returns a list containing the results.


In functional languages, it is not possible to modify an index value at each iteration, that’s why there is no for or while loop.
Instead we browse the lists with functions such as map.

12.2. Improving the presentation of images

  1. add the CSS classes "columns" and "is-multiline" to the div that includes the list of images

  2. display each thumbnail in a div having the CSS classes "column" and "is-one-quarter"

  3. add a vertical spacing between the form and the answers

12.3. Expected result


12.4. Improving the display of the error message

  1. use a Bulma notification to improve the presentation of the error message

  2. a click on the button must hide the notification

  3. the launch of a new search must hide the notification


HTML for displaying a notification with Bulma:

<div class="notification is-danger">
  <button class="delete"></button>
  Lorem ipsum dolor sit amet, consectetur

12.5. Expected outcome


The code for this step is available here

13. Conclusion

This is the end of this first approach to Elm.

We hope you start seeing what makes Elm a so delightful language:

  • reliable applications: no errors in production

  • simplicity of the tools

  • useful error messages

  • fast compilation

  • packages cohesion: no conflict between packages

  • performance: it compares to the fastest JS frameworks, an is better than the most popular of them (source)

  • light weight of the JS compiled files (source)

  • welcoming community (try Slack)

  • last but not least, refactoring without risk, which probably is its killer feature.


14. Going Further

If you want to go further, there are books and online resources:

You can also complete the app you started here, by example by displaying the large version of the image when clicking a thumbnail.