Handling errors in Express.js applications is crucial for a reliable, user-friendly, and maintainable application.
Error handling ensures that any unexpected issues that arise in your Express.js application are managed gracefully. By properly handling errors, you can prevent the application from crashing and offer users clear, helpful messages when something goes wrong.
In Express.js, middleware functions can be used to handle errors at different points in the request lifecycle. Basic error-handling middleware uses four parameters: err
, req
, res
, and next
.
const express = require('express');
const app = express();
// Sample route that throws an error
app.get('/error', (req, res, next) => {
next(new Error('Something went wrong!'));
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack); // Log error details for debugging
res.status(500).json({ message: 'Internal Server Error' });
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
/error
route.next()
.res.status(500)
sets the HTTP status to 500
(Internal Server Error), and a JSON response is sent.Output: When visiting http://localhost:3000/error
, you’ll see {"message": "Internal Server Error"}
and the error details will be printed in the console.
Express allows you to create custom error handlers to manage different types of errors uniquely. This enables you to tailor error responses based on the type of error.
// 404 handler middleware for unknown routes
app.use((req, res, next) => {
res.status(404).json({ message: 'Resource not found' });
});
This middleware catches all requests that don’t match any defined route, returning a 404 status code with a custom message.
In asynchronous code, such as in async
functions or when working with Promises, errors may not automatically propagate. You can handle these with try-catch
blocks or use a wrapper function to handle errors in a DRY (Don’t Repeat Yourself) way.
app.get('/async-error', async (req, res, next) => {
try {
// Simulate an asynchronous error
const result = await someAsyncFunction();
res.json(result);
} catch (error) {
next(error); // Pass the error to the error-handling middleware
}
});
In this example, any errors thrown in someAsyncFunction()
are caught by catch
, and next(error)
sends it to the error-handling middleware.
Logging errors is critical for diagnosing issues in production. The morgan
package can log HTTP requests, and winston
can log error details to files or external services.
npm install morgan winston
const morgan = require('morgan');
const winston = require('winston');
// Initialize morgan for request logging
app.use(morgan('combined'));
// Initialize winston for error logging
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'errors.log' })
]
});
app.use((err, req, res, next) => {
logger.error(err.message); // Log error message to errors.log
res.status(500).json({ message: 'Internal Server Error' });
});
morgan
logs all HTTP requests, which is helpful for debugging.winston
logs error messages to errors.log
, providing a record of issues for future reference.Custom error classes help categorize errors and add more information, making them easier to handle differently based on the error type.
class NotFoundError extends Error {
constructor(message) {
super(message);
this.status = 404;
}
}
app.get('/user/:id', (req, res, next) => {
const user = findUserById(req.params.id);
if (!user) {
return next(new NotFoundError('User not found'));
}
res.json(user);
});
app.use((err, req, res, next) => {
res.status(err.status || 500).json({ message: err.message });
});
NotFoundError
class extends Error
, allowing you to set a custom status
property for 404 errors.Output: If the user is not found, the response will be {"message": "User not found"}
, with a 404 status.
When building public APIs, it’s essential to provide user-friendly, descriptive error messages.
app.use((err, req, res, next) => {
if (err instanceof ValidationError) {
return res.status(400).json({ error: err.message, field: err.field });
}
res.status(err.status || 500).json({ message: 'Something went wrong, please try again later' });
});
Integrate error-tracking services like Sentry or Rollbar for real-time error monitoring.
npm install @sentry/node
npm install @sentry/node
const Sentry = require('@sentry/node');
Sentry.init({ dsn: 'YOUR_SENTRY_DSN' });
app.use(Sentry.Handlers.errorHandler());
// Example route to trigger error
app.get('/sentry-error', (req, res) => {
throw new Error('Sentry test error');
});
Testing your error handlers ensures they work as expected.
// Example Jest test
test('GET /error should return 500', async () => {
const response = await request(app).get('/error');
expect(response.status).toBe(500);
expect(response.body.message).toBe('Internal Server Error');
});
Effective error handling in Express.js is essential for building reliable, maintainable applications. By implementing basic and custom error-handling middleware, logging errors with tools like Winston, and integrating error-tracking services like Sentry, you can provide users with a better experience and quickly diagnose issues. Happy Coding!❤️