---
title: Routing in Elm with Hop and Mailboxes
teaser: Elegant routing in Elm with Hop.
tags: elm,web
author: Josh Clayton
published_on: 2016-05-03
---

I've been enjoying [Elm] for a while now and, in the interest of understanding
what types of applications can be built with the language, I decided to try
out [Hop], a routing library for single page applications that, as of version
4.0, supports [push state]. It also supports [StartApp] out of the box.

[Elm]: http://elm-lang.org
[Hop]: https://github.com/sporto/hop
[push state]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
[StartApp]: https://github.com/evancz/start-app

## Getting Started with Hop

Hop requires a bit of wiring together to get everything working; the areas
to change are:

1. **Model**: The model is responsible for maintaining knowledge of where the
   user is
2. **View**: The view is responsible for rendering the correct content based
   on where the user is
3. **Update**: The updater/reducer is responsible for handling navigation
   changes and updating the model accordingly
4. **Main.elm**: `Main.elm` manages all of our outbound ports; because Hop is
   interacting with the browser, this data transfer happens over a port
5. **Router**: The router is responsible for managing possible routes and
   their constraints

With this understanding, let's go through each section one-by-one. In this
example, I'll be referencing an Elm application [available on GitHub] that
follows [the Elm architecture]; the application lives in the `HopExample`
namespace.

[available on GitHub]: https://github.com/joshuaclayton/elm-hop-mailboxes-example
[the Elm architecture]: https://github.com/evancz/elm-architecture-tutorial

### Model Changes

As mentioned previously, the model now needs to maintain two pieces of
information: the current route (referenced by a union type of all available
routes), and the current location, made available from the browser.

First, let's open `HopExample.App.Model` and update our imports:

```elm
-- HopExample.App.Model

import Hop.Types exposing (Location, newLocation)
import HopExample.Router exposing (Route(LoadingRoute))
```

(We'll come back to `HopExample.Router` below.)

`Hop.Types` provides the `Location` type and a `newLocation` function, which is
the base state of `Location`. You can see that, from our `HopExample.Router`,
we're making available `LoadingRoute`, a [nullary data constructor] that's part
of the `Route` union type.

[nullary data constructor]: https://en.wikipedia.org/wiki/Nullary_constructor

With our imports set, let's update the `Model` and `initialModel`, which
previously only contained `recordList`:

```elm
-- HopExample.App.Model

type alias Model =
  { recordList : HopExample.RecordList.Model.Model
  , route : Route
  , location : Location
  }

initialModel : Model
initialModel =
  { recordList = HopExample.RecordList.Model.initialModel
  , route = LoadingRoute
  , location = newLocation
  }
```

`StartApp` will use `route` and `location` from `initialModel` to set the
initial state of the router.

### View Changes

Next up is the view.

Our view is arguably the most straightforward set of changes; we'll need to
introduce a `case` to check on the `Route` union type and make decisions about
what to render based on the outcome.

First, the imports:

```elm
-- HopExample.App.View

import HopExample.Router exposing (Route(..))
```

All we'll need is the `Route` type and its corresponding data constructors,
since we'll be referencing each by name.

Let's take a look at the `view` and `pageContent` functions, which previously
always rendered a list of records:

```elm
-- HopExample.App.View

view : Signal.Address Action -> Model -> Html
view address model =
  section
    []
    [ pageHeader
    , pageContent address model
    , pageFooter
    ]

pageContent : Signal.Address Action -> Model -> Html
pageContent address model =
  case model.route of
    HomeRoute ->
      HopExample.RecordList.View.view model.recordList

    LoadingRoute ->
      h3 [] [ text "Loading..." ]

    NotFoundRoute ->
      h3 [] [ text "Page Not Found" ]
```

Instead of always calling `HopExample.RecordList.View.view model.recordList`,
we now pattern match against `model.route` and modify the `Html` returned. As
Elm forces us to be exhaustive in our pattern-matching (no [partial functions]
are allowed), we now have a clear picture of what our `Route` looks like:

[partial functions]: https://wiki.haskell.org/Partial_functions

```elm
-- HopExample.Router

type Route
  = HomeRoute
  | LoadingRoute
  | NotFoundRoute
```

Neat!

### Update Changes

Almost there; let's dig into changes to `HopExample.App.Update`.

First, the imports:

```elm
-- HopExample.App.Update

import Hop.Types exposing (Location)
import HopExample.Router exposing (Route)
```

Next, we'll update our `Action`:

```elm
-- HopExample.App.Update

type Action
  = NoOp ()
  | ApplyRoute ( Route, Location )
```

I've added a `NoOp ()` (`()` can be read as "void" - it's a placeholder for
any type that's discarded), and `ApplyRoute ( Route, Location )`. `ApplyRoute`
is the name recommended by Hop; the only real requirement is that it's used to
handle the signal provided by the router (`Signal ( Route, Location )`).

Finally, let's wire up the `HopExample.App.Update.update` function:

```elm
-- HopExample.App.Update

update : Action -> Model -> ( Model, Effects Action )
update action model =
  case action of
    NoOp () ->
      ( model, Effects.none )

    ApplyRoute ( route, location ) ->
      ( { model | route = route, location = location }, Effects.none )
```

We can see that `ApplyRoute` is being used to return a model with new state,
namely `route` and `location`.

### Update `Main.elm`

Almost there! Let's wire together the router's `Signal ( Route, Location )`
into our `inputs : List ( Signal Action )`. First, our imports:

```elm
-- Main

import HopExample.Router exposing (router)

-- old version
-- import HopExample.App.Update exposing (Action, init, update)

-- new version
import HopExample.App.Update exposing (Action(ApplyRoute), init, update)
```

We were already importing `Action`, but we also want the `ApplyRoute` data
constructor.

Next, let's use `Signal.map` to convert the `router`'s `Signal ( Route,
Location )` to `Signal Action`:

```elm
hopRouteSignal : Signal Action
hopRouteSignal =
  Signal.map ApplyRoute router.signal
```

Don't forget to include this new signal:

```elm
inputs : List (Signal Action)
inputs =
  [ hopRouteSignal ]
```

Finally, let's wire up the outbound port Hop gives us:

```elm
port routeRunTask : Task () ()
port routeRunTask =
  router.run
```

[Task] might look familiar, especially if you've seen Elm's [Result] or Haskell's
[Either]. It's a binary type constructor with a failure type and success type.
For this port, we're using the aforementioned void to represent that we can
disregard both the success and failure types entirely in this function.

[Task]: http://package.elm-lang.org/packages/elm-lang/core/latest/Task
[Result]: http://package.elm-lang.org/packages/elm-lang/core/latest/Result
[Either]: https://hackage.haskell.org/package/base-4.8.2.0/docs/Data-Either.html

### Routing Everything Together

With this groundwork in place, let's build out `HopExample.Router`. Recall
that we've already identified what our `Route` union type will look like:

```elm
-- HopExample.Router

module HopExample.Router (Route(..)) where

type Route
  = HomeRoute
  | LoadingRoute
  | NotFoundRoute
```

Next, in `Main.elm`, we saw reference to a `router` function; let's make a few
changes to expose it:

```elm
-- HopExample.Router

module HopExample.Router (Route(..), router) where

import Hop
import Hop.Matchers exposing (match1)
import Hop.Types exposing (Router, PathMatcher)

type Route
  = HomeRoute
  | LoadingRoute
  | NotFoundRoute

router : Router Route
router =
  Hop.new
    { hash = False
    , basePath = "/"
    , matchers = matchers
    , notFound = NotFoundRoute
    }

matchers : List (PathMatcher Route)
matchers =
  [ match1 HomeRoute ""
  ]
```

Here, we configure our router:

* `hash = False` configures the router to use push state versus hash state
* `basePath = "/"` configures the application to run at the site root
* `matchers = matchers` configures the list of routes we'll match on
* `notFound = NotFoundRoute` configures the data type when the router can't
  determine the route

### Running the Application

One thing to note: if you're using a server to run the application, ensure the
server doesn't also have a router handling the URLs used by the Elm
application. In most cases, you'll want to configure a catch-all route so
anything matching the `basePath` in the router is sent to the correct page.

With this in place, we can determine what to render to a user based on the
route.

## Elm Effects for Navigation

We already covered that managing browser push state is handled by an outbound
port; how do we trigger that effect, though?

First, let's expose `HopExample.Router.navigateTo` (we'd already made `Route`
available):

```elm
-- HopExample.App.Update

import HopExample.Router exposing (Route, navigateTo)
```

As well as a new `Action` type:

```elm
-- HopExample.App.Update

type Action
  = NoOp ()
  | ApplyRoute ( Route, Location )
  | NavigateTo String
```

And pattern-match to cover the behavior:

```elm
-- HopExample.App.Update

update : Action -> Model -> ( Model, Effects Action )
update action model =
  case action of
    NoOp () ->
      ( model, Effects.none )

    ApplyRoute ( route, location ) ->
      ( { model | route = route, location = location }, Effects.none )

    NavigateTo path ->
      ( model, Effects.map NoOp (navigateTo path) )
```

Now, if we send the action `NavigateTo "/my/path/name"` to a `Signal.Address
Action` address, our `navigateTo` function will trigger an appropriate effect
and we can trust that we'll be taken to the correct URL.

Let's define `navigateTo`, which really just leverages
`Hop.Navigate.navigateTo` but uses our configuration.

```elm
-- HopExample.Router

navigateTo : String -> Effects.Effects ()
navigateTo =
  Hop.Navigate.navigateTo routerConfig

router : Router Route
router =
  Hop.new routerConfig

routerConfig : Config Route
routerConfig =
  { hash = False
  , basePath = "/"
  , matchers = matchers
  , notFound = NotFoundRoute
  }
```

We've moved a few things around here. First, we've extracted `routerConfig`
to its own function, since it's now used by both `router` and `navigateTo`.
Second, we've had to update what we're importing (namely, `import Effects` and
adding `Config` to the list of types from `Hop.Types`).

From a view, we can now do:

```elm
exampleView : Signal.Address Action -> Model -> Html
exampleView address model =
  button
    [ onClick address (NavigateTo "/my/path") ]
    [ text "Go somewhere" ]
```

If you've built larger applications in Elm, however, you may recognize a few
warning signs:

1. The only way to trigger a route change with this setup is to pass around an
   address to send actions to. For deeply nested components, this may be
   unwieldy because of signal forwarding or circular dependencies.
2. For large architectures with multiple namespaces, effect management
   (calling `navigateTo`) is spread across each `HopExample.*.Update.update`.
3. Managing path generation across files may introduce churn if paths change,
   and there's no canonical place to identify how paths are generated.
4. This doesn't work with traditional anchor tags out of the box, since
   browsers handle them with default behavior.

## Elm Mailboxes to the Rescue

Earlier, we saw in `Main.elm` that we could take a `Signal ( Route, Location
)` from our `router` and turn it into a `Signal Action` with [`Signal.map`].
What if we could do the same thing with `NavigateTo`?

[`Signal.map`]: http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Signal#map

Let's look at the type signature for `Signal.map`:

```elm
Signal.map : (a -> result) -> Signal a -> Signal result
```

So, we apply `map` to a function of `a` to `result` and then to a `Signal a`,
and get a `Signal result`. Working backwards, let's imagine we want to add a
new signal to `Main.elm` that we can add to our `inputs : List (Signal
Action)`. With that, we know our new `navigations` signal needs to be of type
`Signal Action`:

```elm
navigations : Signal Action
navigations = -- what goes here?
```

Let's look at `NavigateTo`. Remember, `NavigateTo` is a unary data constructor -
that is, it needs to be applied to one argument (in this case, a string) to
return an `Action`:

```elm
NavigateTo : (String -> Action)
```

Take a look at the type signature for `Signal.map` above. We know we want to
get to `Signal Action` based on our definition of navigations, so we can start
fleshing out the details:

```elm
navigations : Signal Action
navigations =
  Signal.map NavigateTo functionThatReturnsSignalofString
```

We need to make available a `functionThatReturnsSignalofString`; enter
[`Signal.mailbox`], a record with an `address` to send messages to and a
`signal` of sent messages. Let's open up `HopExample.Router`:

[`Signal.mailbox`]: http://package.elm-lang.org/packages/elm-lang/core/3.0.0/Signal#mailbox

```elm
-- HopExample.Router

routerMailbox: Signal.Mailbox String
routerMailbox =
  Signal.mailbox ""
```

Here, we're declaring a new mailbox with an initial state of `""`.
As mentioned above, `routerMailbox` exposes two properties: `address` and
`signal`. With that information in hand, and recognizing that the mailbox is
for values of type `String`, things start to fall into place.

Let's finish up our work in `Main.elm`. First, expose `routerMailbox` from
`HopExample.Router`:

```elm
-- Main

import HopExample.Router exposing (router, routerMailbox)
```

And update `inputs` and `navigations` with the final changes:

```elm
-- Main

inputs : List (Signal Action)
inputs =
  [ hopRouteSignal, navigations ]

navigations : Signal Action
navigations =
  Signal.map NavigateTo routerMailbox.signal
```

Next, let's wrap up the pesky route generation and event handling by defining
functions including `rootPath : String` and `linkTo : String -> List Attribute ->
List Html -> Html`:

```elm
-- HopExample.Router

rootPath : String
rootPath =
  "/"

linkTo : String -> List Attribute -> List Html -> Html
linkTo path attrs inner =
  let
    customLinkAttrs =
      [ href path
      , onClick' routerMailbox.address path
      ]
  in
    a (attrs ++ customLinkAttrs) inner

onClick' : Signal.Address a -> a -> Attribute
onClick' addr msg =
  onWithOptions
    "click"
    { defaultOptions | preventDefault = True }
    value
    (\_ -> Signal.message addr msg)
```

Our `onClick'` (pronounced "onclick prime") is a custom `onClick` that
prevents default browser behavior and sends our message (e.g.
`"/path/to/go/to"` to an address (`routerMailbox.address`). You'll need to
update your imports for the router to compile, since there are now functions
generating HTML:

```elm
-- HopExample.Router

import Html exposing (Html, Attribute, a)
import Html.Attributes exposing (href)
import Html.Events exposing (onWithOptions, defaultOptions)
import Json.Decode exposing (value)
```

You'll also want to export `linkTo` and any path helpers (e.g. `rootPath`) and
use those in place of traditional `a` for any internal anchors, since they
override default behavior.

## Routing Exploration

Finally, let's touch on exploring what you can do with routes. Within my
`HopExample`, there's a `HopExample.Record.Model.Model` with an `id`, among
other properties. Let's look at the path helper, the route matcher, and how
we'd use `linkTo`:

```elm
-- HopExample.Router

import Hop.Matchers exposing (int, match1, match2)
import HopExample.Record.Model

type Route
  = HomeRoute
  | RecordRoute Int
  | LoadingRoute
  | NotFoundRoute

matchers : List (PathMatcher Route)
matchers =
  [ match1 HomeRoute ""
  , match2 RecordRoute "/records/" int
  ]

recordPath : HopExample.Record.Model.Model -> String
recordPath record =
  "/records/" ++ (record.id |> toString)
```

In a view, you'd then be able to:

```elm
-- HopExample.*.View

import HopExample.Router exposing (linkTo, recordPath)

renderRecord : HopExample.Record.Model.Model -> Html
renderRecord model =
  let
    recordHeader =
      model.record ++ " by " ++ model.artist
    recordPublished =
      "Released in " ++ (model.yearReleased |> toString)
  in
    div
      []
      [ h3
          []
          [ linkTo (recordPath record) [] [ text recordHeader ]
          ]
      , p [] [ text recordPublished ]
      ]
```

I'll leave handling finding the correct `HopExample.Record.Model.Model` from
the list of records as an exercise for the reader.

## Wrapping Up

With Hop configured, and the approach to handling navigation changes across
all levels of the app within one function (`linkTo`), we should be in a place
where any subsequent changes to both the routing and behavior is entirely
encapsulated.
