--- name: backend-routing description: Create or update backend controllers, route wiring, queues, and workers in this WatchNode repository. Use when adding endpoints, nesting child controllers, wiring auth or validation, defining queue-backed flows, or following the backend routing and worker patterns. --- # Backend Routing Use the existing backend routing primitives instead of raw Express setup. ## Follow The Existing Shape - Implement `Controller` and return `Routes` from `routes()`. - Decorate controller classes with `@injectable()`. - Use `router("/resource", (router) => { ... })` for controller-local route definitions. - Controllers can be mounted on a sub-path by a parent route tree in `backend/src/app.ts`. - Use `group(...)`, `controllers(...)`, and `mount(...)` when composing nested route trees. ## Build Handlers The Repo Way - Return handlers from methods like `getThing()`, `createThing()`, `updateThing()`. - Wrap every async route handler with `handler(async (req, res) => { ... })`. - Prefer explicit `return` statements for final responses and short-circuit branches. - Validate request input with `validate(req, { params, query, body })`. - Read request-scoped values through exported context helpers backed by `context()`. - Use `HTTPError` for expected error paths instead of ad hoc response branching. - Delegate business logic to injected services instead of embedding it in the controller. ## Choose Route Layout Carefully - Apply `router.use(this.authMiddleware.middleware())` when the whole controller is protected. - Apply middleware per-route when only some endpoints require auth or rate limiting. - For nested resources, prefer the existing relative child-controller pattern `mount(router, controllers(...))`. ## Create Middlewares - Put shared middleware under `backend/src/shared/middlewares/`. - Middlewares do not need the container unless they actually depend on injected services or facades. - Middleware classes can expose one or more methods that return middleware values. - `middleware()` is a common method name for custom middlewares that need injected dependencies. - Prefer `handler(...)` for middleware implementations as well, especially when the middleware is async. - Decorate middleware classes with `@injectable()` only when they depend on injected services or facades. - Define and export request-scoped context values in the middleware file that sets them. - Use `next(HTTPError.status(...))` for expected middleware failures. - Keep middleware focused on request setup, auth, gating, or normalization. Push business logic into services. When creating a new middleware-backed request value: 1. Define the context export in the middleware file. 2. Build the middleware that resolves the value, preferably through `handler(...)` when async work is involved. 3. Call `.set(req, value)` inside the middleware. 4. Mount the middleware at the controller or route level. 5. Read the value downstream through the exported context helper. ## Match Existing Response Style - Return JSON responses with a `message` field and usually a `data` field. - Use `res.status(201).json(...)` for successful creates. - Use `res.json(...)` for standard reads/updates/deletes unless the surrounding module already follows a different convention. - Preserve the response shape already consumed by frontend API services if editing an existing endpoint. ## Validate Like The Existing Controllers - Use inline `z.object(...)` schemas for small params/query validation. - Reuse module validation files for request bodies. - Keep new route and param naming clean and consistent. If an existing endpoint has deprecated naming, avoid spreading it to new code unless you are intentionally maintaining compatibility. ## Wire New Controllers End To End When creating a brand-new backend controller: 1. Create the controller in the target module. 2. Inject required services and middleware through the constructor. 3. Add the controller to `backend/src/app.ts`. 4. Add or reuse validation schemas as needed. 5. Confirm the mounted path is correct after parent route groups are applied. 6. Update tests and frontend API consumers if the new route is externally used. ## Check Before Finalizing - Compare with a nearby controller in the same module before inventing a new pattern. - Confirm whether the route should be documented by Swagger comments. - Confirm whether auth, rate limiting, audit logging, or nested mounting should also be added. - If middleware sets request-scoped values, read them through the exported context helpers instead of inventing a new request property pattern or adding compatibility wrappers. Read [references/patterns.md](references/patterns.md) when you need concrete repo examples and a small creation checklist. ## Add Queues And Workers The Repo Way - Define queue names, job data types, and queue factory functions in `backend/src/shared/queues/queues.ts`. - Add queue registry wiring in `backend/src/shared/queues/index.ts`. - Implement workers under `backend/src/workers/`. - Register new workers in `backend/src/workers/index.ts`. - If the API should initialize or schedule the queue on startup, update `backend/src/app.ts`. When adding a queue-backed feature: 1. Define the queue and its payload type. 2. Add the enqueue point in the service or orchestration flow. 3. Implement the worker that consumes the queue. 4. Register the worker and any startup scheduling. 5. Check whether tests or admin/debug surfaces also need updates.