Routing and Controllers
This skill defines how to keep routes thin and controllers consistent.
1. Routes should only route
✅ Good:
ts
router.post("/users", authMiddleware, (req, res) => userController.create(req, res));
❌ Bad (business logic in routes):
ts
router.post("/users", async (req, res) => {
// 200 lines of business logic…
});
2. Controller responsibilities
Controllers should:
- •Parse inputs (params/body/query)
- •Validate inputs
- •Call services
- •Return consistent HTTP responses
Recommended pattern: a base controller helper for success/error responses.
ts
export class BaseController {
protected ok(res: Response, body: unknown) {
res.status(200).json(body);
}
protected fail(res: Response, status: number, message: string) {
res.status(status).json({ error: message });
}
}
3. Error handling pattern
Controllers should:
- •Catch errors once
- •Capture to monitoring (with context)
- •Map domain errors to HTTP status codes
ts
async create(req: Request, res: Response) {
try {
const input = createUserSchema.parse(req.body);
const user = await this.userService.create(input);
return res.status(201).json(user);
} catch (err) {
monitor.captureException(err, { tags: { layer: "controller", route: "/users" } });
return res.status(500).json({ error: "Internal server error" });
}
}
Prefer typed domain errors and explicit mapping for known failures (validation, not found, conflict).
Related Skills
- •
architecture-overview - •
validation-patterns