Routes
Declare the endpoints of a collection, who may call them and what each one is allowed to do
A route is one endpoint of a collection: an HTTP method, a path, a handler that does the work and the settings that say who may call it and what it may do. This page lists every field of a route, the rules that StarkForge enforces when the server starts and the two mistakes that cause confusing symptoms.
A Route List With Notes
Routes live in the routes list of a collection. This list gives anyone read access, lets editors and administrators write, and keeps deletion for administrators:
// src/collections/products.ts
import { collection, defineRoutes, fields } from "@starklabs/forge";
import { z } from "zod";
export const products = collection({
type: "crud",
route: "products",
model: "Product",
schema: {
name: fields.requiredString,
price: fields.requiredNumber,
},
validations: {
create: { name: z.string().min(1).max(120), price: z.number().min(0) },
update: { name: z.string().min(1).max(120).optional(), price: z.number().min(0).optional() },
},
routes: defineRoutes([
// Anyone may list and read products.
{ method: "get", path: "/", handler: "readAll", auth: "public" },
{ method: "get", path: "/:id", handler: "read", auth: "public" },
// Signed-in editors and administrators may create and change products.
{ method: "post", path: "/", handler: "create", auth: "authenticated", roles: ["editor", "admin"], validation: "create" },
{ method: "patch", path: "/update/:id", handler: "update", auth: "authenticated", roles: ["editor", "admin"], validation: "update" },
// Only administrators may delete.
{ method: "delete", path: "/delete/:id", handler: "remove", auth: "admin" },
]),
});Route Fields
| Field | Type | Default | What it does |
|---|---|---|---|
method | "get", "post", "put", "patch" or "delete" | Required | The HTTP method |
path | string | Required | The path after the collection route, starting with /. A segment that starts with a colon is a parameter |
handler | string | Required | The built-in handler that does the work. The valid names depend on the collection type. See Handlers |
validation | string or false | None | The name of a group in the collection's validations. See Validation |
auth | access level | Depends on the type | Who may call the route. See Authorization |
roles | string[] | None | The roles that may call the route, checked on top of auth |
ownership | "self" or "owner" | "owner" | Which field says who owns a document. "owner" compares the owner field with the signed-in user. "self" compares the document's own _id, which suits the user model |
config | object | None | Per-route database settings: populate, populateSelect, hiddenFields, targetField and maxBulkSize. See Relations and Hidden and Encrypted Fields |
files | upload[] | None | The file fields the route accepts. See Uploads |
mode | "credentials" or "otp" | The signupMode or loginMode setting | Overrides the sign-in mode of one sign-up or sign-in route in an auth collection |
encryptedFields | string[] | None | Body fields that are encrypted before saving. A trailing ? marks a field optional |
decryptedFields | string[] | None | Encrypted fields that the response returns in plain text |
hashedFields | string[] | None | Body fields that are hashed with Argon2id before saving |
actions | object | None | Your own code or emails that run before or after the handler. See Actions |
The access levels are "public", "authenticated", "admin" and "admin-or-owner". An access level is not a role: roles lists values such as editor, and the Authorization page explains how the two combine.
Default Access Levels
A route without auth gets the default of its collection type:
| Collection type | Default auth |
|---|---|
crud | "authenticated" |
account | "authenticated" |
auth | "public" |
health | "public" |
A CRUD route that you leave without auth therefore needs a signed-in user. If you configured no sign-in at all, those routes answer 401 to everyone, and StarkForge logs a warning when it starts.
Paths
A path is any string that Express accepts, and it is added to the collection's route. These shapes are the convention that this documentation follows, but none of them is required:
| Path | Used for |
|---|---|
"/" | Listing and creating |
"/:id" | Reading one document |
"/update/:id" and "/delete/:id" | Updating and deleting one document |
"/bulk", "/delete-many" and "/delete-all" | Batch operations |
"/:id/addFile", "/:id/updateFile" and "/:id/deleteFile" | File handlers |
Two rules matter. The parameter of a handler that works on one document must be named id, because the handlers read req.params.id, and its value must be a MongoDB id of exactly 24 hexadecimal characters. Any other value is answered with status 400 and the code INVALID_OBJECT_ID.
Express checks routes in the order you list them, so put specific paths before parameterized ones. With /:id first, a request for /products/latest matches /:id and fails:
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"success": false,
"message": "The id in the URL is not valid.",
"code": "INVALID_OBJECT_ID",
"requestId": "0add8d45-8e76-4f44-8342-32dda7eab138"
}List { method: "get", path: "/latest", handler: "readAll", auth: "public" } before the /:id entry and the same request reaches its own route.
Rules Checked at Start-up
StarkForge checks every route when the server starts, and reports all problems of a collection at once, as Collections shows. These are the rules:
| Rule | What it prevents |
|---|---|
The handler must exist for the collection type | A typo such as list instead of readAll |
A validation name must exist in validations, and validation: true is rejected | A route that points at rules that do not exist |
removeAll must use auth: "admin" or "admin-or-owner" | Any caller wiping a whole collection |
roles must be a non-empty list, and cannot be used with auth: "public" or auth: "admin" | A role check that can never pass or that repeats adminRoles |
Routes that check roles need verifyAccessUser to stay on | Every caller being refused because no role was loaded |
files is only valid on create, update, addFile, updateFile and deleteFile | An upload that nothing reads |
updateFile and deleteFile need exactly one files entry, and addFile needs at least one | A file route that does not know which field it works on |
encryptedFields and decryptedFields need the masterKey option | Data that cannot be sealed or opened |
Every entry in actions must be a customAction or an emailAction | An action that never runs |
The messages quote the method and path of the route, so you can find it. This one comes from a roles entry on a public route:
- GET /things/mine: 'roles' cannot be used on a 'public' route: nobody is signed in, so nobody has a role. Use auth: 'authenticated'.A Route Without Validation Accepts No Body
A route that reads a body must name a validation group. Without validation, StarkForge discards every body field, so the handler works on an empty body. Two symptoms follow.
A create route without validation saves an empty document. When the schema requires a field, the client sees an error about a field it did send:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"success": false,
"message": "name: Path `name` is required.",
"code": "VALIDATION_FAILED",
"issues": [
{
"path": "name",
"message": "Path `name` is required.",
"code": "required"
}
],
"requestId": "0add8d45-8e76-4f44-8342-32dda7eab138"
}An update route without validation is quieter: it answers 200, returns the document, and changes nothing, because the fields you sent were discarded. When a write seems to be ignored, check the validation key first.
Where to Go Next
Validation
Write the rules that check each request body.
Authorization
Combine access levels, roles and ownership.
:::