joshburgess/elm-route-craft

Composable, bidirectional SPA routing without code generation


License
BSD-3-Clause
Install
elm-package install joshburgess/elm-route-craft 1.0.0

Documentation

elm-route-craft

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.

Quick Start

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".

Features

  • Bidirectional routing - parse and build URLs from a single codec definition, no duplication
  • Typed query parameters - withQuery, parseFull, toFullPath, and a record-friendly succeed/with/build pipeline give you typed query params alongside path params
  • Rich segment primitives - int, string, float, uuid, nonEmptyString, plus splat for variable-length tails and custom for anything else
  • Constrained segments - intRange min max, slug, and enum validate values at parse time and refuse to build URLs that would not round-trip
  • Case-insensitive literals - literalCi parses 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/fromUrlHash for static hosts that cannot rewrite paths
  • Active link helpers - startsWith for highlighting nav links when the current route is under a section
  • Canonicalization - canonicalize / redirectTo drive redirect-to-canonical handlers without users threading parse |> toPath themselves
  • Strict and Result-typed query parsing - Query.required plus parseQueryStrict (returns Maybe) or parseQueryResult (returns Result (List String) a listing missing keys)
  • Raw query helpers - Query.update, drop, toList, fromList, lookup, merge, preserve, except, canonicalString for query operations that don't fit the typed pipeline
  • Composable page hierarchy - App.page supports static, sandbox, and element-style pages via init/update/view/subscriptions
  • Shared state - first-class shared record threaded through page init, view, and subscriptions
  • 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

Comparison

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

Examples

  • 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, the Query.succeed/with/build pipeline, and Query.withDefault.
  • hash-routing - Same SPA shape, but routes live in the URL fragment via parseHash / toHashPath / fromUrlHash for static hosts that cannot rewrite paths.

Documentation

Installation

elm install joshburgess/elm-route-craft

License

BSD-3-Clause