This separation makes each layer independently testable and maintainable. You can swap out the database layer, add new business rules to the service layer, or change HTTP handling — without touching the other layers.
Layers
Routes
Map HTTP endpoints to their respective controller functions. Each resource has its own route file registered in
index.ts.index.ts
Middlewares
Intercept requests before they reach controllers. Two primary middlewares are applied:
authMiddleware— Validates theAuthorization: Bearer <token>header, callsauthService.validateToken(), and attachesreq.user(withuserId,email, andname) to the request object for downstream use.errorHandler— Global error handler registered last in the middleware stack. Catches any error passed tonext(error)and serializes it into a structured JSON response.
middlewares/auth.ts
Controllers
Receive the Express
req and res objects, extract and lightly validate parameters, then delegate all business logic to the service layer. Controllers do not query the database directly.Services
The business logic layer. Services enforce domain rules — such as gamification point calculations, streak increments, and ownership checks — before calling repository methods. All
AppError subclass throws happen here.Request lifecycle
errorHandler middleware via Express’s next(error) mechanism.
Error handling
All application errors extend the baseAppError class, which attaches an HTTP status code to every thrown error.
errors/AppError.ts
| Error class | HTTP status | Typical usage |
|---|---|---|
AppError | 500 | Base class; generic server error |
BadRequestError | 400 | Invalid input, ownership violations |
UnauthorizedError | 401 | Missing or invalid JWT |
NotFoundError | 404 | Resource does not exist |
ConflictError | 409 | Duplicate resource |
Middleware stack
The full middleware stack as registered inindex.ts:
index.ts
cors and express.json() run on every request, route handlers run next, and errorHandler is registered last so it can catch errors from all preceding middleware and routes.