Error Handling Project
title: "API Error Handling Project — Build Robust Error Middleware" description: "Build a complete error handling system for an Express API with custom error classes, async wrappers, centralized middleware, structured responses, and logging." date: 2026-06-28 lastmod: 2026-06-28 weight: 30 tags: [apis, error-handling] }
Build a complete error handling system for a REST API including custom error classes, async error wrappers, centralized error middleware, structured responses, and logging.
What You'll Learn
- Implementing a production-grade error handling system
- Integrating error codes, trace IDs, and structured responses
- Testing error handling middleware
Why It Matters
This project brings together all error handling concepts into a reusable system you can apply to any API.
Project Structure
error-handling-project/
src/
errors/
AppError.js
NotFoundError.js
ValidationError.js
AuthError.js
middleware/
errorHandler.js
asyncHandler.js
notFoundHandler.js
routes/
users.js
app.js
tests/
errors.test.js
Error Classes
// errors/AppError.js
class AppError extends Error {
constructor(message, statusCode = 400, code = null) {
super(message);
this.statusCode = statusCode;
this.code = code || this.constructor.name;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
toJSON() {
return {
error: this.code,
message: this.message,
status: this.statusCode
};
}
}
// errors/NotFoundError.js
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} '${id}' not found`, 404, 'NOT_FOUND');
this.resource = resource;
this.resourceId = id;
}
toJSON() {
return {
...super.toJSON(),
resource: this.resource,
resource_id: this.resourceId
};
}
}
Middleware
// middleware/errorHandler.js
const { v4: uuidv4 } = require('uuid');
function errorHandler(err, req, res, next) {
const traceId = uuidv4();
const isOperational = err.isOperational || false;
const statusCode = err.statusCode || 500;
const code = err.code || 'INTERNAL_ERROR';
console.error(`[${traceId}]`, {
method: req.method,
path: req.path,
userId: req.user?.id,
error: err.message,
stack: err.stack
});
const response = {
error: code,
message: isOperational ? err.message : 'An unexpected error occurred',
trace_id: traceId,
...(err.toJSON ? err.toJSON() : {})
};
res.status(statusCode).json(response);
}
module.exports = errorHandler;
Testing
// tests/errors.test.js
const request = require('supertest');
const app = require('../src/app');
describe('Error handling', () => {
it('returns 404 for unknown routes', async () => {
const res = await request(app).get('/unknown');
expect(res.status).toBe(404);
expect(res.body.error).toBe('NOT_FOUND');
});
it('returns structured 400 for validation errors', async () => {
const res = await request(app)
.post('/users')
.send({});
expect(res.status).toBe(400);
expect(res.body.error).toBe('VALIDATION_ERROR');
expect(res.body.fields).toBeDefined();
});
});
Common Mistakes
1. Not Testing Error Cases
Integration tests should cover every error scenario.
2. Inconsistent Response Format
Test that all errors return the same JSON structure.
3. No Error Middleware in Production
Without proper middleware, unhandled errors crash the process.
4. Missing 404 Handler
The default 404 body is HTML, not JSON.
5. Not Handling Process-Level Errors
Add handlers for uncaught exceptions and unhandled rejections.
FAQ
What's Next
Your error handling knowledge is complete. Explore API Pagination or API Caching for more API design patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro