Add a Backend Route
This guide shows how to record backend route metadata and implement its handler. add-route writes metadata to package.json; it does not create a working endpoint.
Use this when a backend endpoint needs explicit manifest metadata, schema references, or backend-inspect visibility. For the default full app path, keep document pages in src/frontend/pages/** and keep form-handling logic in src/backend/module.ts, then add manifest-backed route entries as those backend surfaces stabilize.
Prerequisites
- A Webstir workspace initialized via
webstir init .... - Backend source at
src/backend/.
Steps
- Add a route with defaults:
webstir add-route users --workspace "$PWD"- Writes
GET /api/userstowebstir.moduleManifest.routesinpackage.json.
- Specify method and path explicitly when needed:
webstir add-route users --workspace "$PWD" --method POST --path /api/users
- Attach metadata so documentation and tooling stay in sync:
webstir add-route accounts --workspace "$PWD" --summary "List accounts" --description "Returns the current tenant accounts" --tags accounts,api- Schema references follow the
kind:name@sourceformat described in the CLI reference. Example:webstir add-route accounts --workspace "$PWD" --params-schema zod:AccountParams@src/backend/contracts/accounts.ts --response-schema zod:AccountList@src/backend/contracts/accounts.ts
- Declare HTML-first route primitives when the route is a server-handled form or fragment update:
webstir add-route sign-in --workspace "$PWD" --method POST --path /api/sign-in --interaction mutation --session required --session-write --form-urlencoded --csrfwebstir add-route account-panel --workspace "$PWD" --method POST --path /api/account/panel --interaction mutation --fragment-target account-panel --fragment-mode replace
Wire the handler
After writing the manifest entry, implement the handler in src/backend/module.ts. For example, record webstir add-route hello --path /api/hello --workspace "$PWD", then add this entry to the existing routes array:
{
definition: {
name: 'hello',
method: 'GET',
path: '/api/hello',
summary: 'Return a greeting',
},
handler: () => ({ status: 200, body: { message: 'Hello from Webstir' } }),
},- Preserve the exported module and its
manifest.routes: routes.map((route) => route.definition)mapping. The defaultfullstarter has a deliberately narrow localRouteContexttype for its demo; use the public backend runtime types when your feature needs more context. - A handler has the app's batteries on
ctx:ctx.db(database),ctx.jobs(jobs),ctx.email(email),ctx.files(files), andctx.userwhen the app has sign-in.auth: 'required'on the definition sends signed-out visitors to sign in. - The backend provider auto-loads
build/backend/module.js, logs the manifest summary, and mounts every exported route. No manual registration is required when you editsrc/backend/module.ts.
Verify the manifest
- Run
webstir build --workspace "$PWD"(orwebstir watch --workspace "$PWD"in an app with a server). - Print the manifest summary without starting the dev service:
webstir backend-inspect --workspace "$PWD" - The inspect command rebuilds the backend and lists capabilities, routes, and jobs so you can verify manifest metadata before sharing it with collaborators or publishing packages.
Notes
- The CLI prevents duplicate entries for the same method+path.
- The backend provider also validates the manifest and emits diagnostics on duplicates.
add-routerecords metadata only. Verify the implemented endpoint with an HTTP request or browser, in addition to inspecting the manifest.--session requireddeclares that a session must already exist. The runtime enforces it: requests without a session get401with asession_requirederror before the handler runs, and the rejection never creates a session. The declaration inpackage.jsonis applied to the matchingmethod+pathhandler insrc/backend/module.tseven when that handler states nosessionmetadata; if the two disagree onsession.mode,requiredwins andbackend-inspect/startup report a warning. It does not check identity. Gate signed-in access in the handler (or a request hook) withctx.author your own session data.--form-urlencoded,--csrf, and--fragment-*only declare the contract. Your handler insrc/backend/module.tsstill needs to implement the actual form, redirect, or fragment behavior.- Schema references can point at Zod files (
zod:Type@path/to/file.ts), JSON schema, or ts-rest routers. They only record metadata; implement request and response validation in application code.
See Also
- CLI reference:
../reference/cli.md#add-route - Backend provider:
../explanations/solution.md(manifest ingestion)