Error Handling
Implement robust error handling in Express — catch validation errors, handle missing resources, and create a centralized error middleware.
Error Handling
A production REST API must handle errors gracefully. Bad input, missing resources, database failures — your API should respond with clear error messages and appropriate status codes instead of crashing or returning cryptic errors.
Types of Errors
| Error Type | Status Code | Example |
|---|---|---|
| Validation error | 400 | Missing required fields, invalid data types |
| Not found | 404 | Requesting a resource that does not exist |
| Cast error | 400 | Invalid MongoDB ObjectId format |
| Duplicate key | 409 | Creating a resource that already exists (unique field) |
| Server error | 500 | Database connection lost, unexpected crash |
Try-Catch in Route Handlers
router.get('/:id', async (req, res) => {
try {
const ninja = await Ninja.findById(req.params.id);
if (!ninja) {
return res.status(404).json({ error: 'Ninja not found' });
}
res.json(ninja);
} catch (err) {
if (err.name === 'CastError') {
return res.status(400).json({ error: 'Invalid ID format' });
}
res.status(500).json({ error: 'Server error' });
}
});
Centralized Error Middleware
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
console.error(err.stack);
// Mongoose validation error
if (err.name === 'ValidationError') {
const messages = Object.values(err.errors).map(e => e.message);
return res.status(400).json({ errors: messages });
}
// Mongoose cast error (invalid ObjectId)
if (err.name === 'CastError') {
return res.status(400).json({ error: 'Invalid ID format' });
}
// Duplicate key error
if (err.code === 11000) {
return res.status(409).json({ error: 'Duplicate key error' });
}
// Default server error
res.status(500).json({ error: 'Internal server error' });
}
module.exports = errorHandler;
Error-handling middleware has four parameters — (err, req, res, next). Express recognizes the four-parameter signature and only calls it when an error is passed to next().
Using the Error Middleware
// index.js
const errorHandler = require('./middleware/errorHandler');
app.use('/api/ninjas', ninjaRoutes);
// Error middleware must come AFTER routes
app.use(errorHandler);
Passing Errors to the Middleware
router.post('/', async (req, res, next) => {
try {
const ninja = new Ninja(req.body);
const savedNinja = await ninja.save();
res.status(201).json(savedNinja);
} catch (err) {
next(err); // Passes the error to the error middleware
}
});
Instead of handling errors in every route, call next(err) to forward the error to the centralized error handler. This keeps route handlers clean and ensures consistent error responses.
404 Handler (Route Not Found)
// After all routes, before error middleware
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
This middleware catches any request that did not match a defined route and returns a 404 response.
Key Takeaways
- Always wrap async route handlers in try-catch and call
next(err)to forward errors - Error middleware has four parameters
(err, req, res, next)and must be defined after all routes - Handle specific error types (validation, cast, duplicate) with appropriate status codes
- A centralized error handler ensures consistent error responses across the entire API
- Always add a 404 catch-all middleware after all routes for unmatched URLs