Composable, bidirectional SPA routing for Elm 0.19.1. Define your routes once as a typed codec pipeline and get both URL parsing and URL building from the same definition - no code generation, no CLI, no magic. It is a library you wire up yourself, not a framework that owns your app structure.
module Main exposing (main)
import App
import Html exposing (a, div, h1, text)
import Html.Attributes exposing (href)
import Route exposing (Router)
import Url exposing (Url)
type Route
= Home
| Blog
| BlogPost Int
| NotFound
router : Router Route
router =
Route.oneOf
[ Route.top Home
, Route.codec Blog (\r -> case r of Blog -> Just () ; _ -> Nothing)
|> Route.literal "blog"
, Route.codec BlogPost (\r -> case r of BlogPost id -> Just ( id, () ) ; _ -> Nothing)
|> Route.literal "blog"
|> Route.int
]
toRoute : Url -> Route
toRoute url =
Route.fromUrl router url |> Maybe.withDefault NotFound
main =
App.create
{ router = router
, toRoute = toRoute
, toPage = \route _ ->
App.page
{ init = \_ -> ( (), Cmd.none )
, update = \_ m -> ( m, Cmd.none )
, view = \_ _ -> div [] [ h1 [] [ text (Debug.toString route) ] ]
, subscriptions = \_ _ -> Sub.none
}
, shared =
{ init = \_ -> ( (), Cmd.none )
, update = \_ s -> ( s, Cmd.none )
, subscriptions = \_ -> Sub.none
}
}Route.parse router "/blog/42" returns Just (BlogPost 42).
Route.toPath router (BlogPost 42) returns Just "/blog/42".
- Bidirectional routing - parse and build URLs from a single codec definition, no duplication
-
Typed query parameters -
withQuery,parseFull,toFullPath, and a record-friendlysucceed/with/buildpipeline give you typed query params alongside path params -
Rich segment primitives -
int,string,float,uuid,nonEmptyString, plussplatfor variable-length tails andcustomfor anything else -
Constrained segments -
intRange min max,slug, andenumvalidate values at parse time and refuse to build URLs that would not round-trip -
Case-insensitive literals -
literalCiparses any case but builds the canonical form, useful for SEO redirect hygiene -
Nested routes with shared prefixes -
nest "blog" [ ... ]groups routes under a common prefix and can be nested arbitrarily -
Mountable base paths -
withBasePath "/admin"mounts the entire router at a sub-path -
Hash routing -
parseHash/toHashPath/fromUrlHashfor static hosts that cannot rewrite paths -
Active link helpers -
startsWithfor highlighting nav links when the current route is under a section -
Canonicalization -
canonicalize/redirectTodrive redirect-to-canonical handlers without users threadingparse |> toPaththemselves -
Strict and Result-typed query parsing -
Query.requiredplusparseQueryStrict(returnsMaybe) orparseQueryResult(returnsResult (List String) alisting missing keys) -
Raw query helpers -
Query.update,drop,toList,fromList,lookup,merge,preserve,except,canonicalStringfor query operations that don't fit the typed pipeline -
Composable page hierarchy -
App.pagesupports static, sandbox, and element-style pages viainit/update/view/subscriptions -
Shared state - first-class
sharedrecord threaded through pageinit,view, andsubscriptions - No code generation - define routes in plain Elm, the compiler checks your extractor tuples against your constructors
- No CLI required - install and start writing routes
| Feature | elm-route-craft | elm-land | elm-spa | Vanilla Elm |
|---|---|---|---|---|
| Code generation required | No | Yes | Yes | No |
| CLI required | No | Yes | Yes | No |
| Bidirectional routing | Yes | No | No | Manual |
| Typed query parameters | Yes | Partial | No | Manual |
| Nested route prefixes | Yes | File-based | No | Manual |
| Page types (init/update/view/subs) | Yes | Yes | Yes | Yes |
| Shared state | Yes | Yes | Partial | Manual |
| Route guards / onEnter hooks | Yes | Partial | No | Manual |
| Composable layouts | Yes | Yes | No | Manual |
| Plain Elm, no framework lock-in | Yes | No | No | Yes |
- minimal - 3-page SPA with no shared state and no layouts. The fastest way to see the full wiring.
- blog - Multi-page blog with nested routes, shared state, and typed query parameters on the search page.
- dashboard - Authenticated dashboard with layouts, route guards, and shared user state.
-
features - Tour of the additional path primitives and helpers:
splat,float,nonEmptyString,uuid,startsWith, theQuery.succeed/with/buildpipeline, andQuery.withDefault. -
hash-routing - Same SPA shape, but routes live in the URL fragment via
parseHash/toHashPath/fromUrlHashfor static hosts that cannot rewrite paths.
- docs/GUIDE.md - End-to-end walkthrough from installation to a full SPA
-
docs/API.md - Full API reference for
RouteandApp - docs/ARCHITECTURE.md - Design decisions and internals
- docs/MIGRATION.md - Migration guide from elm-spa, elm-land, or hand-rolled routing
elm install joshburgess/elm-route-craft
BSD-3-Clause