ReferenceEdit on GitHub

Routing conventions#

What a file under src/main/scala/app/ mounts, how the generated table is ordered, and what the framework reserves. Route, RouteTable and PathPattern are in the API. Routing by file layout is the reasoning.

The file name table#

filemethoddef calledpath suffix
Index.scalaGETindex
New.scalaGET`new`/new
Show.scalaGETshow
Edit.scalaGETedit/edit
Create.scalaPOSTcreate
Update.scalaPUTupdate
Destroy.scalaDELETEdestroy
any other Name.scalaGETname, decapitalised/name, lower-cased

The def lives on the object the file declares, app.<dirs>.<FileName>, takes a Request and returns a Response. A custom name is always a GET; a custom POST is a Create.scala in a directory of its own, app/health/Create.scala for POST /health.

Directory segments#

directorysegment
booksthe literal books
_id:id, captures one segment
__rest*rest, captures everything left, including nothing

A catch-all anywhere but last, and a parameter name used twice in one pattern, fail the boot. A trailing slash is ignored on both the pattern and the path.

Emit order#

Handwritten routes first, sorted most static first: segment by segment, a literal before :name before *rest; a shorter path after a longer one when the ranks tie; then by path text, then by method. Derived routes follow, model by model in fully-qualified-name order, each model's seven in the order Index, New, Show, Edit, Create, Update, Destroy. The table is matched in that order and never re-sorted.

Precedence#

situationoutcome
a handwritten and a derived route on one method and paththe handwritten one is served, the derived one is dropped, a warning at boot and in eezo routes
an earlier pattern matches everything a later one wouldlegal; a warning naming both
a derived form page whose submit target isn't mountedlegal; a warning naming the page and the target
two handwritten or two derived routes on one method and paththe boot fails
a path matches but no method does405, with Allow
nothing matches404

The generated file#

target/scala-<version>/src_managed/main/io/eezo/generated/Routes.scala, package io.eezo.generated, object Routes, one member you call: def table(): RouteTable. Every row carries // from <source file>. A compile error on a row means the def the row names doesn't have the handler's shape, and the fix is in the file the comment names.

The file also holds, when there are rows that need them:

  • storeFor[A], which picks each derived model's store: a scoped one for a model declaring Owned, a JdbcStore for a model with a Table when eezo-db is on the compile classpath, an InMemoryStore otherwise.
  • guardFor[A], which looks up the model's or the page's Guarded. When the build names eezo-auth in a configuration the compiler sees, no Guarded in scope is a compile error naming the type; otherwise it's Guarded.public.
  • identify, the composition of every declaration's naming, which the table runs on a socket upgrade.

A candidate model is any case class with a derives clause, at the top level or nested only in objects. One nested in a class, a trait or a block, or indented in a file that uses significant indentation, gets a warning and no row.

The reserved prefix#

/eezo is the framework's, on every server:

pathwhatwhen
GET /eezo/healthok, 200, no cookie read or writtenalways; asked before your table
WS /eezo/reloadthe dev server's reload socketdev only; asked before your table
WS /eezo/live/:pagethe live socketLiveApp; appended after your table
GET /eezo/live.jsthe live clientLiveApp; appended after your table
POST /eezo/sync, POST /eezo/freezethe drift page's actionsonly while the drift page is served

A guard's own routes are not under the prefix: GET /login, POST /login and POST /logout by default, relative to the mount.

URLs and mounts is what Route.under does to a table.