Elixir API and Elm SPA - Part 3
Tags: elm, spa
Published: 2018-06-18 20:03:21

---
title: Elixir API and Elm SPA - Part 3
published: true
description: Creating the Elm app and Routing setup
tags: elm, spa
canonical_url: https://blog.miguelcoba.com/elixir-api-and-elm-spa-part-3
---

# Part 3: Elm App creation and Routing setup

Now we're going to create the Elm application. We're going to use the fantastic [create-elm-app](https://github.com/halfzebra/create-elm-app) because it allows us to create a production ready Elm project with webpack and Hot Module Replacement.

## Series

1. [Part 1 - Elixir App creation](https://dev.to/miguelcoba/elixir-api-and-elm-spa-4hpf)
2. [Part 2 - Adds Guardian Authentication](https://dev.to/miguelcoba/elixir-api-and-elm-spa---part-2-1pca)
3. Part 3 - Elm App creation and Routing setup
4. [Part 4: Adding Login and Register pages](https://dev.to/miguelcoba/elixir-api-and-elm-spa---part-4-4il0)
5. [Part 5: Persisting session data to localStorage](https://dev.to/miguelcoba/elixir-api-and-elm-spa---part-5-4j1k)


## Install the create-elm-app

```bash
npm install -g create-elm-app
```

## Create and start the app

```bash
create-elm-app toltec-web
cd toltec-web
elm-app start
```

You should see the Elm logo in a browser tab. Let's now to build our app.

## Setting up Tachions

For this project I am going to use the [tachyons](http://tachyons.io/) project to create the interfaces. So let's add that to the _head_ section of index.html

```html
<!-- index.html -->

<link rel="stylesheet" href="https://unpkg.com/tachyons@4.9.1/css/tachyons.min.css" />
```

## Setting up Routing

This app is completely based on Richard Feldman excellent [Elm SPA Example](https://github.com/halfzebra/elm-spa-example). 
This app shares a lot of the low level infrastructure, but removes a few of the high level features that are not needed for our app. It also uses a different directory structure that is vertically organised instead of horizontally spread along the Data/, Requests/, Page/, and View/ directories. You'll find that the routing, Model shape, Messages, and Coders/Decoders are very much, if not completely, the same. Thanks Richard, for all the hard work :)

Before adding Elm code, we need to add some dependencies that we are going to use to setup the routing and navigation.

```bash
elm-app install elm-lang/navigation
elm-app install evancz/url-parser
```

For this part we'll have only a few routes:

- Home: #/
- Login: #/login
- Logout: #/logout
- Register: #/register

Let's create a new file named Route.elm in the src/ directory

```elm
-- src/Route.elm

module Route exposing (Route(..), fromLocation, href, modifyUrl)

import Html exposing (Attribute)
import Html.Attributes as Attr
import Navigation exposing (Location)
import UrlParser as Url exposing ((</>), Parser, oneOf, parseHash, s, string)


type Route
    = Home
    | Root
    | Login
    | Logout
    | Register


route : Parser (Route -> a) a
route =
    oneOf
        [ Url.map Home (s "")
        , Url.map Login (s "login")
        , Url.map Logout (s "logout")
        , Url.map Register (s "register")
        ]


routeToString : Route -> String
routeToString page =
    let
        pieces =
            case page of
                Home ->
                    []

                Root ->
                    []

                Login ->
                    [ "login" ]

                Logout ->
                    [ "logout" ]

                Register ->
                    [ "register" ]
    in
        "#/" ++ String.join "/" pieces


href : Route -> Attribute msg
href route =
    Attr.href (routeToString route)


modifyUrl : Route -> Cmd msg
modifyUrl =
    routeToString >> Navigation.modifyUrl


fromLocation : Location -> Maybe Route
fromLocation location =
    if String.isEmpty location.hash then
        Just Root
    else
        parseHash route location
```

This creates a Route type with the possible routes and a _route_ function that will be passed to the _parseHash_ function to help it determine which route corresponds to the current location in the browser url bar.
The _fromLocation_ function is the responsible of mapping between browser location bar and our app Routes.

Next we add a single message to set a route in our app. Create a Messages.elm file:

```elm
-- src/Messages.elm

module Messages exposing (Msg(..))

import Route exposing (Route)


type Msg
    = SetRoute (Maybe Route)
```

For now we only have this message to set a new Route in our app.


And before going further, add this Util.elm copied from Richard's app:

```elm
-- src/Util.elm

module Util exposing ((=>), pair)


(=>) : a -> b -> ( a, b )
(=>) =
    (,)


{-| infixl 0 means the (=>) operator has the same precedence as (<|) and (|>),
meaning you can use it at the end of a pipeline and have the precedence work out.
-}
infixl 0 =>


{-| Useful when building up a Cmd via a pipeline, and then pairing it with
a model at the end.
session.user
|> User.Request.foo
|> Task.attempt Foo
|> pair { model | something = blah }
-}
pair : a -> b -> ( a, b )
pair first second =
    first => second
```

Our initial Model.elm is like this:

```elm
-- src/Model.elm

module Model exposing (Model, initialModel, Page(..), PageState(..), getPage)

import Json.Decode as Decode exposing (Value)


type Page
    = Blank
    | NotFound
    | Home
    | Login
    | Register


type PageState
    = Loaded Page
    | TransitioningFrom Page


type alias Model =
    { pageState : PageState
    }


initialModel : Value -> Model
initialModel val =
    { pageState = Loaded Blank
    }


getPage : PageState -> Page
getPage pageState =
    case pageState of
        Loaded page ->
            page

        TransitioningFrom page ->
            page
```

Initially the app will manage a tiny set of pages as you can see. Following Richards app we'll have either a fully loaded page or we'll be in a state of transition from the current page to a new one.
The Model therefore, holds the current PageState, that is initially a Blank page.

The initial app is slowly taking shape. Let's add now the Update.elm file:

```elm
-- src/Update.elm

module Update exposing (update, init)

import Json.Decode as Decode exposing (Value)
import Model exposing (Model, initialModel, Page(..), PageState(..), getPage)
import Messages exposing (Msg(..))
import Navigation exposing (Location)
import Route exposing (Route)
import Util exposing ((=>))


init : Value -> Location -> ( Model, Cmd Msg )
init val location =
    updateRoute (Route.fromLocation location) (initialModel val)


updateRoute : Maybe Route -> Model -> ( Model, Cmd Msg )
updateRoute maybeRoute model =
    case maybeRoute of
        Nothing ->
            { model | pageState = Loaded NotFound } => Cmd.none

        Just Route.Home ->
            { model | pageState = Loaded Home } => Cmd.none

        Just Route.Root ->
            model => Route.modifyUrl Route.Home

        Just Route.Login ->
            { model | pageState = Loaded Login } => Cmd.none

        Just Route.Logout ->
            model => Cmd.none

        Just Route.Register ->
            { model | pageState = Loaded Register } => Cmd.none


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    updatePage (getPage model.pageState) msg model


updatePage : Page -> Msg -> Model -> ( Model, Cmd Msg )
updatePage page msg model =
    case ( msg, page ) of
        ( SetRoute route, _ ) ->
            updateRoute route model
```

This module exposes two functions, the first one is _init_ that takes an initial value and a location and returns the first tuple (Model, Cmd Msg) that will bootstrap our app state. As you can see, when the app starts, the state will depend only on two things the Value (that we'll see later) and the Location in the url.

The other exposed function is the _update_ function. This is very simple because it delegates the real job to the _updatePage_. This last one will be where the app logic reside. Right now it doesn't do much, it only knows about one single message, the SetRoute message and when gets one, it updates the app route.

Finally, the _updateRoute_ is where the logic specific to route changes is declared. Depending on the route passed, it establishes a specific page as loaded. Right now, the Logout route does nothing.

The View.elm file is the following:

```elm
-- src/View.elm

module View exposing (..)

import Html exposing (..)
import Model exposing (Model, Page(..), PageState(..))
import Messages exposing (Msg(..))
import Page.Page as Page exposing (ActivePage)
import Page.Home as Home
import Page.NotFound as NotFound
import Session.Login as Login
import Session.Register as Register


view : Model -> Html Msg
view model =
    case model.pageState of
        Loaded page ->
            viewPage True page

        TransitioningFrom page ->
            viewPage False page


viewPage : Bool -> Page -> Html Msg
viewPage isLoading page =
    let
        frame =
            Page.frame isLoading
    in
        case page of
            NotFound ->
                NotFound.view
                    |> frame Page.Other

            Blank ->
                Html.text "Loading Maya!"

            Home ->
                Home.view
                    |> frame Page.Home

            Login ->
                Login.view
                    |> frame Page.Login

            Register ->
                Register.view
                    |> frame Page.Register
```

This is quite simple, but uses a lot of new modules. Essentially, what it does is to retrieve the page from the model and then render the appropriate view.
The _viewPage_ function receives the page and then renders a view chosen among several ones. 

This is how the views are defined:

```elm

-- src/Page/NotFound.elm

module Page.NotFound exposing (view)

import Html exposing (Html, h1, div, text)


view : Html msg
view =
    div []
        [ h1 [] [ text "The page you requested was not found!" ] ]

-- src/Page/Home.elm

module Page.Home exposing (view)

import Html exposing (..)


view : Html msg
view =
    div []
        [ h1 [] [ text "Toltec" ]
        , p [] [ text "Welcome to Toltec!" ]
        ]

-- src/Session/Login.elm

module Session.Login exposing (view)

import Html exposing (..)


view : Html msg
view =
    div []
        [ h1 [] [ text "Login page" ] ]

-- src/Session/Register.elm

module Session.Register exposing (view)

import Html exposing (..)


view : Html msg
view =
    div []
        [ h1 [] [ text "Register page" ] ]
```

They are quite simple, and only render some text so that we can check that the routing works. Notice that we created two new directories: Session/ and Page/.

The missing part is the Page.frame function that takes come content and puts a above around it.

```elm
-- src/Page/Page.elm

module Page.Page exposing (ActivePage(..), frame)

import Html exposing (..)
import Html.Attributes exposing (..)
import Route exposing (Route)


type ActivePage
    = Other
    | Home
    | Login
    | Register


frame : Bool -> ActivePage -> Html msg -> Html msg
frame isLoading activePage content =
    div []
        [ viewHeader activePage isLoading
        , content
        ]


viewHeader : ActivePage -> Bool -> Html msg
viewHeader activePage isLoading =
    nav [ class "dt w-100 border-box pa3 ph5-ns" ]
        [ a [ class "dtc v-mid mid-gray link dim w-25", Route.href Route.Home, title "Home" ]
            [ text "Maya" ]
        , div [ class "dtc v-mid w-75 tr" ] <|
            [ navbarLink activePage Route.Home [ text "Home" ]
            , navbarLink activePage Route.Login [ text "Login" ]
            , navbarLink activePage Route.Register [ text "Register" ]
            ]
        ]


navbarLink : ActivePage -> Route -> List (Html msg) -> Html msg
navbarLink activePage route linkContent =
    let
        active =
            case isActive activePage route of
                True ->
                    "black"

                False ->
                    "gray"
    in
        a [ Route.href route, class "link hover-black f6 f5-ns dib mr3 mr4-ns", class active ] linkContent


isActive : ActivePage -> Route -> Bool
isActive activePage route =
    case ( activePage, route ) of
        ( Home, Route.Home ) ->
            True

        ( Login, Route.Login ) ->
            True

        ( Register, Route.Register ) ->
            True

        _ ->
            False
```

As Richard explains, the _frame_ function adds a header to the content we pass to it. It also takes care of detecting if the page we're showing matches any of the links on the nav bar. For the time being we're ignoring the isLoading parameter.

Having all this in place, we can code the main function. Change Main.elm to this:

```elm
-- src/Main.elm

module Main exposing (main)

import Json.Decode as Decode exposing (Value)
import Navigation
import Model exposing (Model)
import Messages exposing (Msg(..))
import Route exposing (Route)
import Subscriptions exposing (subscriptions)
import Update exposing (update, init)
import View exposing (view)


main : Program Value Model Msg
main =
    Navigation.programWithFlags (Route.fromLocation >> SetRoute)
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }
```

Super simple. It uses the [Navigation.programWithFlags](http://package.elm-lang.org/packages/elm-lang/navigation/latest/Navigation#programWithFlags) to start our app. 
This function takes as the first parameter a (Location -> msg) function, that converts a Location into a message. In our case, the message is SetRoute. The _Navigation.programWithFlags_ function will use this function to obtain a message each time the url changes and will call the _update_ function passing to it this message. In essence, it observes for changes in the URL and triggers an Elm cycle by calling the update function.
Of course, additionally, it can receive a initial value from JavaScript land when the app is bootstraped, but we're not using that right now.

The _subscriptions_ function is defined in the Subscriptions.elm file:

```elm
-- src/Subscriptions.elm

module Subscriptions exposing (subscriptions)

import Model exposing (Model, Page(..), PageState(..), getPage)
import Messages exposing (Msg(..))


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ pageSubscriptions (getPage model.pageState)
        ]


pageSubscriptions : Page -> Sub Msg
pageSubscriptions page =
    case page of
        Blank ->
            Sub.none

        NotFound ->
            Sub.none

        Home ->
            Sub.none

        Login ->
            Sub.none

        Register ->
            Sub.none
```

Now we have all the pieces together and we can test our app. The _elm-app start_ command should have hot reloaded all these changes and the app should be ready. Check the terminal that you don't have any error in the output. It should be like this:

```bash
Compiled successfully!

You can now view toltec-web in the browser.  Local:            http://localhost:3000/
  On Your Network:  http://192.168.1.108:3000/
Note that the development build is not optimized.
To create a production build, use elm-app build.
```

If you check your browser you should see something like this:

![Toltec Home page](https://thepracticaldev.s3.amazonaws.com/i/3ic50eacmqrqd4gcpsel.png)

If you click on the links in the navbar, you'll see that they correctly swap the view rendered according to the page corresponding to the url in the browser.

You can find the source code, with tests, in the repo [here](https://github.com/miguelcoba/toltec-web) in the branch part-03.

Nice, let's wrap it here for now. We have created the Elm app and configured the routing. 

In part 4 we're going to add the login and register pages and connect them to our backend Elixir API.