Error Handling & API Response Format
Standardized enterprise error handling with machine-readable error codes, validation error mapping, and consistent API response contracts.
Error Handling & API Response Format
The SLMS enforces a single, predictable API response contract across all endpoints. Every response — success or failure — follows the same shape, enabling the frontend to interpret responses programmatically via error codes rather than message text parsing.
Response Contracts
Success Response
{
"success": true,
"message": "Book created successfully.",
"data": { ... }
}Error Response
{
"success": false,
"code": "VALIDATION_FAILED",
"message": "Validation failed.",
"errors": {
"email": "Email is required.",
"password": "Must be at least 12 characters."
}
}Error Response (No Field Errors)
{
"success": false,
"code": "RATE_LIMITED",
"message": "Too many requests, please try again later."
}APIError Class
File: backend/utils/apiError.js
class APIError extends Error {
constructor(message, statusCode, code = 'SERVER_ERROR', errors = null, isOperational = true) {
super(message);
this.statusCode = statusCode;
this.code = code; // Machine-readable error identifier
this.errors = errors; // Optional field-level validation errors
this.isOperational = isOperational;
}
}Usage in Services / Controllers
// Simple error
throw new APIError('User not found', 404, 'RESOURCE_NOT_FOUND');
// Validation error with field details
throw new APIError('Validation failed.', 400, 'VALIDATION_FAILED', {
email: 'Email is required.',
password: 'Must be at least 12 characters.'
});Error Handler Middleware
File: backend/middleware/errorHandler.js
The global error handler catches all errors passed to next(err) and maps them to standardized codes:
| Error Source | Mapped Code | Status |
|---|---|---|
Mongoose CastError | RESOURCE_NOT_FOUND | 404 |
Mongoose duplicate key (11000) | DUPLICATE_RESOURCE | 400 |
Mongoose ValidationError | VALIDATION_FAILED | 400 |
JsonWebTokenError | AUTH_INVALID | 401 |
TokenExpiredError | TOKEN_EXPIRED | 401 |
Body-parser SyntaxError | INVALID_PAYLOAD | 400 |
rate-limiter-flexible rejection | RATE_LIMITED | 429 |
| Unhandled / unknown | SERVER_ERROR | 500 |
Automatic Validation Error Extraction
When Mongoose throws a ValidationError, the handler automatically extracts per-field error messages into the errors object:
// Mongoose throws: { errors: { email: { message: "Email is required" }, ... } }
// Handler returns:
{
"success": false,
"code": "VALIDATION_FAILED",
"message": "Validation failed.",
"errors": {
"email": "Email is required"
}
}Standard Error Codes
| Code | HTTP Status | When Used |
|---|---|---|
AUTH_REQUIRED | 401 | Missing or absent authentication |
AUTH_INVALID | 401 | Malformed or tampered JWT |
TOKEN_EXPIRED | 401 | JWT has expired |
INSUFFICIENT_PERMISSIONS | 403 | User lacks required role/permission |
RESOURCE_NOT_FOUND | 404 | Invalid MongoDB ObjectId or missing document |
DUPLICATE_RESOURCE | 400 | Unique constraint violation (e.g., duplicate email) |
VALIDATION_FAILED | 400 | Mongoose schema or Zod validation failure |
INVALID_PAYLOAD | 400 | Malformed JSON in request body |
RATE_LIMITED | 429 | Rate limiter rejection |
SERVER_ERROR | 500 | Unhandled or unknown error |
Frontend Integration
The frontend's Axios interceptor (services/api/client.ts) uses the code field to:
- Look up the error in the Error Code Registry (
constants/errorCodes.ts) - Determine the severity (error / warning / info)
- Route it to the correct UI mechanism (Toast, Banner, Inline Error, or Redirect)
Backend Response → code: "TOKEN_EXPIRED"
│
▼
Error Code Registry → { title: "Session Expired", severity: "warning", action: "Login" }
│
▼
Notification Manager → Toast (warning) + Redirect to /loginThis means pages never manually interpret HTTP status codes. The API client layer handles it automatically.