Instruction file imported from devfullcycle/mba-ia-dev-workflow (
.github/instructions/controllers.instructions.md). Copyright stays with the author.
Controller Pattern
Controllers are individually exported procedural functions — never classes. Each function is a route handler that follows the flow: extract data from request → call use case → return response.
Rules
- One function per action: each handler is an exported named function (
export async function createUser) - No business logic: controllers never validate rules, compute values, or access repositories directly — delegate everything to the use case
- Standard signature:
(req: Request, res: Response, next: NextFunction) => Promise<void> - No silent errors: every error must be propagated — never use empty catch blocks, never swallow exceptions with
console.logwithout re-throwing, never return a generic response ignoring the error. The catch block always delegates to the error middleware vianext(error) - Correct REST status codes:
200— successful read or update201— resource created (POST that creates)204— no response body (e.g., logout, delete)- Never return error status codes manually (4xx/5xx) — the error middleware determines those from use case exceptions
- Explicit data extraction: destructure
req.body,req.params, andreq.queryat the top of the handler, before calling the use case - Direct response:
res.status(xxx).json(result)— no helpers or extra abstractions
Handler structure
import { Router } from "express";
const router = Router();
router.post("/<path>", async (req, res, next) => {
try {
const { name, email } = req.body;
const user = await createUserUseCase.execute({ name, email });
res.status(201).json(user);
} catch (error) {
next(error);
}
});
export default router;