Architect, design, and implement backend API endpoints for Node.js and TypeScript services using strict layered architecture, request validation, structured error handling, and audit logging. Use when building or refactoring routes, controllers, validators, or service-layer API flows.
When building or modifying API endpoints, you MUST adhere to the Strict Layered Architecture. The backend code should be decoupled into specific layers to maintain security, testability, and separation of concerns regardless of the underlying ORM or framework.
The Router is the entry point. It is strictly responsible for wiring up HTTP methods, paths, and middleware. It should contain NO business logic.
Standard Middleware Chain Flow:
authentication: Ensures the user has a valid, active session.validation: Parses and validates incoming payloads (body, query, params) against a schema diagram.authorization: Evaluates RBAC/IAM permissions to ensure the user can act on the target resource.controller: The final destination.Example Implementation:
router.post(
"/",
authenticateLocalStrategy,
validateSchema({ body: createResourceSchema }),
authorizeResource("resource:create"),
createResource,
);
All incoming data (body, query, params) must be validated before hitting the controller using a schema validation library (like Zod, Joi, or Yup).
Rules:
create[Resource]Schema, update[Resource]Schema, etc.400 Bad Request.The Controller acts as the bridge between HTTP requests and the business logic wrapper/service layer.
Rules:
req.body, req.params, req.user, req.ip and pass it down cleanly to the service.try/catch resolving to central next(err)) to prevent unhandled promise rejections crashing the node process.{
"success": true,
"data": { ... },
"message": "Optional success message"
}
Example Implementation:
export const createResource = asyncWrapper(
async (req: Request, res: Response) => {
if (!req.user) throw new UnauthorizedError();
const result = await resourceService.createResource(req.user.id, req.body, {
ipAddress: req.ip,
userAgent: req.headers["user-agent"],
});
res
.status(201)
.json({ success: true, data: result, message: "Created successfully" });
},
);
This layer contains the actual heart of the application. All Database calls, external API integrations, and heavy business logic go here.
Rules:
ValidationError, NotFoundError, ForbiddenError). Do not throw generic JS Errors.CREATE, UPDATE, DELETE) should trigger a standardized Audit Log before successfully returning to the controller.Example Implementation:
async createResource(userId: string, data: DataPayload, auditInfo: AuditInfo) {
// 1. Validate Business Rules (e.g. Does the parent exist?)
// 2. Perform DB Mutation securely
const resource = await dbClient.resource.create({ ... });
// 3. Create Audit Log async
await emitAuditLog({
userId,
action: "CREATE_RESOURCE",
resourceId: resource.id,
ipAddress: auditInfo.ipAddress,
});
return resource;
}
When asked to construct or refactor a new API endpoint natively in a workspace, the agent should: