Express.js Error Handling
Error handling is a crucial aspect of building robust web applications. Express.js provides a flexible mechanism for handling errors. This guide covers key concepts, examples, and best practices for implementing error handling in Express.js applications.
Key Concepts of Error Handling
- Error Handling Middleware: Middleware functions specifically designed to handle errors.
- next Function: The function used to pass control to the next middleware function, including error handling.
- Error Object: An object that contains information about an error.
Basic Error Handling Middleware
Create error handling middleware to catch and respond to errors:
Example: Basic Error Handling
// error-handling.js
const express = require('express');
const app = express();
const port = 3000;
// Middleware to simulate an error
app.get('/error', (req, res, next) => {
const err = new Error('Something went wrong!');
err.status = 500;
next(err); // Pass the error to the error handling middleware
});
// Basic error handling middleware
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({ error: err.message });
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Handling 404 Errors
Create middleware to handle 404 errors when no route matches the request:
Example: Handling 404 Errors
// not-found.js
const express = require('express');
const app = express();
const port = 3000;
// Middleware to handle 404 errors
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err); // Pass the error to the error handling middleware
});
// Basic error handling middleware
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({ error: err.message });
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Using Asynchronous Error Handling
Handle errors in asynchronous code using try-catch blocks and the next function:
Example: Asynchronous Error Handling
// async-error-handling.js
const express = require('express');
const app = express();
const port = 3000;
// Asynchronous route with error handling
app.get('/async-error', async (req, res, next) => {
try {
// Simulate an asynchronous error
throw new Error('Asynchronous error occurred!');
} catch (err) {
next(err); // Pass the error to the error handling middleware
}
});
// Basic error handling middleware
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({ error: err.message });
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Advanced Error Handling
Implement advanced error handling by customizing the error response:
Example: Advanced Error Handling
// advanced-error-handling.js
const express = require('express');
const app = express();
const port = 3000;
// Middleware to simulate an error
app.get('/error', (req, res, next) => {
const err = new Error('Something went wrong!');
err.status = 500;
next(err); // Pass the error to the error handling middleware
});
// Advanced error handling middleware
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({
message: err.message,
// Only provide stack trace in development
stack: process.env.NODE_ENV === 'development' ? err.stack : {}
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Centralized Error Handling
Use a centralized error handling strategy to manage errors in a consistent way:
Example: Centralized Error Handling
// centralized-error-handling.js
const express = require('express');
const app = express();
const port = 3000;
// Custom error class
class AppError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
}
// Middleware to simulate an error
app.get('/error', (req, res, next) => {
next(new AppError('Something went wrong!', 500)); // Pass the custom error to the error handling middleware
});
// Centralized error handling middleware
app.use((err, req, res, next) => {
if (!(err instanceof AppError)) {
err = new AppError('Internal Server Error', 500);
}
res.status(err.status || 500);
res.json({
message: err.message,
// Only provide stack trace in development
stack: process.env.NODE_ENV === 'development' ? err.stack : {}
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Best Practices for Error Handling
- Use Centralized Error Handling: Implement a centralized error handling middleware to manage errors consistently across your application.
- Handle 404 Errors: Create middleware to handle 404 errors when no route matches the request.
- Use Custom Error Classes: Define custom error classes to represent different types of errors in your application.
- Log Errors: Implement logging to record errors for debugging and monitoring purposes.
- Hide Sensitive Information: Avoid exposing sensitive information in error responses. Provide detailed stack traces only in development mode.
Testing Error Handling
Test your error handling logic using frameworks like Mocha, Chai, and Supertest:
Example: Testing Error Handling
// Install Mocha, Chai, and Supertest
// npm install --save-dev mocha chai supertest
// test/error-handling.test.js
const chai = require('chai');
const expect = chai.expect;
const request = require('supertest');
const express = require('express');
const app = express();
app.get('/error', (req, res, next) => {
const err = new Error('Something went wrong!');
err.status = 500;
next(err); // Pass the error to the error handling middleware
});
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({ error: err.message });
});
describe('GET /error', () => {
it('should return a 500 error', (done) => {
request(app)
.get('/error')
.expect(500)
.end((err, res) => {
if (err) return done(err);
expect(res.body).to.have.property('error').that.equals('Something went wrong!');
done();
});
});
});
// Define test script in package.json
// "scripts": {
// "test": "mocha"
// }
// Run tests with NPM
// npm run test
Key Points
- Error Handling Middleware: Middleware functions specifically designed to handle errors.
- next Function: The function used to pass control to the next middleware function, including error handling.
- Follow best practices for error handling, such as using centralized error handling, handling 404 errors, using custom error classes, logging errors, and hiding sensitive information.
Conclusion
Error handling is a crucial aspect of building robust web applications. By understanding and implementing the key concepts, examples, and best practices covered in this guide, you can effectively manage errors in your Express.js applications. Happy coding!