Skip to content

Routing

Semitra routing is declarative and Rails-shaped, but targeted at Cloudflare-native JSON APIs that pair with the opinionated React Router 7 frontend stack.

Generated starter apps default to a versioned JSON API surface with api(...):

import { api, drawRoutes, get } from "@semitra/cli";
export default drawRoutes(() => {
api(() => {
get("health", "health#show");
});
});

The example app shows a broader reference surface:

import { drawRoutes, get, namespace, post, resources } from "@semitra/cli";
export default drawRoutes(() => {
resources("posts");
get("health", "health#show");
post("rpc", "rpc#create");
namespace("api", () => {
namespace("v1", () => {
resources("posts");
get("health", "health#show");
});
});
});

That keeps the public surface compact: a posts resource, a health check, an RPC endpoint, and a versioned API namespace all map cleanly to controller actions.

  • resources("posts") generates conventional CRUD-style routes.
  • api() applies the default /api/v1 JSON route shape for generated starters.
  • get() and post() create explicit routes.
  • namespace() groups paths and controller names.
  • route metadata is compiled into the generated app manifest.

Semitra also supports route defaults such as version and format through the DSL.

The reference app uses the same shape in config/routes.ts:

resources("posts");
get("health", "health#show");
post("rpc", "rpc#create");
namespace("api", () => {
namespace("v1", () => {
resources("posts");
get("health", "health#show");
});
});

Each route definition ultimately resolves:

  • HTTP method
  • path
  • controller identifier
  • action name
  • optional name
  • optional version or format metadata
  • optional params contract
  • optional middleware references

That metadata is what SemitraApp uses to instantiate the correct controller and dispatch the action.

Semitra merges:

  • path params
  • query params
  • request body params

The merged set is exposed through this.params. If a route defines a params contract, Semitra validates it before controller execution and stores the typed result in validatedParams.

Keep routes declarative:

  • route to controllers, not arbitrary functions
  • use versioned namespaces when you want public API stability
  • let controllers own orchestration
  • let route params contracts define request shape early