S
Core Concepts

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:

TypeScript
// 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

FieldTypeDefaultWhat it does
method"get", "post", "put", "patch" or "delete"RequiredThe HTTP method
pathstringRequiredThe path after the collection route, starting with /. A segment that starts with a colon is a parameter
handlerstringRequiredThe built-in handler that does the work. The valid names depend on the collection type. See Handlers
validationstring or falseNoneThe name of a group in the collection's validations. See Validation
authaccess levelDepends on the typeWho may call the route. See Authorization
rolesstring[]NoneThe 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
configobjectNonePer-route database settings: populate, populateSelect, hiddenFields, targetField and maxBulkSize. See Relations and Hidden and Encrypted Fields
filesupload[]NoneThe file fields the route accepts. See Uploads
mode"credentials" or "otp"The signupMode or loginMode settingOverrides the sign-in mode of one sign-up or sign-in route in an auth collection
encryptedFieldsstring[]NoneBody fields that are encrypted before saving. A trailing ? marks a field optional
decryptedFieldsstring[]NoneEncrypted fields that the response returns in plain text
hashedFieldsstring[]NoneBody fields that are hashed with Argon2id before saving
actionsobjectNoneYour 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 typeDefault 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:

PathUsed 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:

Text
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:

RuleWhat it prevents
The handler must exist for the collection typeA typo such as list instead of readAll
A validation name must exist in validations, and validation: true is rejectedA 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 onEvery caller being refused because no role was loaded
files is only valid on create, update, addFile, updateFile and deleteFileAn upload that nothing reads
updateFile and deleteFile need exactly one files entry, and addFile needs at least oneA file route that does not know which field it works on
encryptedFields and decryptedFields need the masterKey optionData that cannot be sealed or opened
Every entry in actions must be a customAction or an emailActionAn 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:

Text
 - 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:

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

:::