Route Builder Pattern
defineRoute() is the standard pattern for all endpoints in cod-server. Every new endpoint must use it. All existing domains are already migrated — if you encounter a raw createRoute() route, convert it using the migration workflow.
Two workflows:
- Creating new endpoints — see NEW-ENDPOINTS.md
- Migrating existing endpoints — see MIGRATION.md
Real-world examples from the codebase: EXAMPLES.md
Quick Reference
import { defineRoute } from "@/lib/route-builder";
import { SCOPES } from "../../../../cod-shared/rbac/scopes";
const myRoute = defineRoute({
method: "get", // "get" | "post" | "patch" | "delete" | "put"
path: "/my-resource",
auth: { scope: SCOPES.RESOURCE_READ },
tags: ["MyTag"],
summary: "List resources",
operationId: "listResources",
query: MyFiltersSchema, // optional
params: MyParamsSchema, // optional
body: MyBodySchema, // optional
responses: { // optional: only when default responses are not enough
200: { description: "...", content: { "application/json": { schema: MySchema }}},
422: { description: "Business rule violation" },
},
handler: handlers.myHandler,
});
router.openapi(myRoute.route, myRoute.handler);
Auth Strategies
auth: "public" // No auth — no middleware, security omitted from spec (webhook receivers)
auth: "api-key" // Any authenticated dashboard user
auth: "admin" // Admin role only (stores, users)
auth: "store" // Store API key (X-Store-API-Key) — store/*, abandoned-orders storefront
auth: { scope: SCOPES.ORDERS_READ } // Specific scope (most common)
auth: { anyOf: [SCOPES.READ, SCOPES.ALL] } // Any of multiple scopes
auth: { allOf: [SCOPES.READ, SCOPES.ADMIN] } // All scopes required
Non-JSON request bodies use bodyContent (raw content map):
bodyContent: {
"multipart/form-data": {
schema: z.object({ file: z.instanceof(File).openapi({ type: "string", format: "binary" }) }),
},
}
Header schemas (e.g. svix webhook receivers): headers: MyHeadersSchema.
What defineRoute() generates automatically
middleware— fromauthstrategysecurity—ApiKeyAuthorStoreAuth- Standard error responses: 400, 401, 403, 404, 500
tags— inferred from path if not providedoperationId— from handler name if not provided
Override only what you need to customize via responses: {...}.
Key Rules
Always use SCOPES constants — never magic strings
- ✅
auth: { scope: SCOPES.ORDERS_READ } - ❌
auth: { scope: "orders:read" }
- ✅
Register specific paths before param paths
- ✅
POST /bulk-dispatchbeforeGET /{id} - ✅
GET /default/rulesbeforeGET /{id}
- ✅
Schemas come from domain files in
@/openapi/schemas- ✅
import { OrderListItemSchema } from "@/openapi/schemas" - ❌ Inline anonymous schemas for entities that have domain schemas
- ✅
Always run typecheck + tests after migrating
cd cod-server && npm run typecheck && npm test
Files
- SKILL.md — this file
- MIGRATION.md — how to migrate existing endpoints
- NEW-ENDPOINTS.md — how to create new endpoints
- EXAMPLES.md — real examples from wilayas and orders