Error handling is a critical aspect of web application development. In Express.js, error-handling middleware plays a vital role in catching and responding to errors that occur during the request-response cycle. This chapter will cover everything you need to know about error-handling middleware, from basic concepts to advanced techniques, with detailed explanations and practical examples.
Error-handling middleware is a function that takes four arguments: err
, req
, res
, and next
. It is used to handle errors that occur during the execution of route handlers and other middleware. In Express.js, error-handling middleware functions are defined after all other middleware and route handlers.
The simplest form of error-handling middleware catches errors and responds with a generic message.
const express = require('express');
const app = express();
app.get('/', (req, res) => {
throw new Error('Something went wrong!');
});
// Basic error-handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Output: Navigating to http://localhost:3000/
results in a 500 status response with “Something broke!”.
Custom error-handling middleware can provide more detailed error messages and responses.
const express = require('express');
const app = express();
app.get('/', (req, res) => {
throw new Error('Something went wrong!');
});
// Custom error-handling middleware
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).json({ error: err.message });
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Output: Navigating to http://localhost:3000/
results in a 500 status response with a JSON object: { "error": "Something went wrong!" }
.
Express.js can handle both synchronous and asynchronous errors with error-handling middleware.
const express = require('express');
const app = express();
app.get('/sync', (req, res) => {
throw new Error('Synchronous error!');
});
app.get('/async', async (req, res, next) => {
try {
throw new Error('Asynchronous error!');
} catch (err) {
next(err);
}
});
// Error-handling middleware
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).json({ error: err.message });
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
next
function.Output: Navigating to /sync
results in a 500 status response with a JSON object: { "error": "Synchronous error!" }
. Navigating to /async
results in a 500 status response with a JSON object: { "error": "Asynchronous error!" }
.
The next
function can be used to pass errors to the error-handling middleware.
const express = require('express');
const app = express();
app.get('/', (req, res, next) => {
const err = new Error('Something went wrong!');
next(err);
});
// Error-handling middleware
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).json({ error: err.message });
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
next
function is used to pass the error to the error-handling middleware.Output: Navigating to http://localhost:3000/
results in a 500 status response with a JSON object: { "error": "Something went wrong!" }
.
Centralized error handling involves creating a single error-handling middleware function that handles all errors in the application.
const express = require('express');
const app = express();
// Routes
app.get('/', (req, res) => {
throw new Error('Something went wrong!');
});
app.get('/user', (req, res) => {
res.send('User route');
});
// Centralized error-handling middleware
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).json({ error: err.message });
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Output: Any error in the application will result in a 500 status response with a JSON object containing the error message.
Always Catch Errors: Ensure all possible errors are caught, especially in asynchronous code.
Provide Meaningful Error Messages: Return user-friendly error messages while logging detailed information for developers.
Use Centralized Error Handling: Centralize your error-handling logic to maintain consistency and reduce redundancy.
Avoid Leaking Sensitive Information: Be cautious not to expose sensitive information in error messages.
Test Error Handling: Regularly test your error-handling logic to ensure it works as expected.
Different types of errors can be handled differently based on their nature.
const express = require('express');
const app = express();
app.get('/', (req, res) => {
throw new Error('Something went wrong!');
});
app.get('/not-found', (req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
// Advanced error-handling middleware
app.use((err, req, res, next) => {
if (err.status === 404) {
res.status(404).json({ error: err.message });
} else {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
}
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Output: Navigating to /not-found
results in a 404 status response with { "error": "Not Found" }
. Navigating to /
results in a 500 status response with { "error": "Internal Server Error" }
.
Creating custom error classes allows for more fine-grained error handling.
const express = require('express');
const app = express();
class NotFoundError extends Error {
constructor(message) {
super(message);
this.status = 404;
}
}
app.get('/not-found', (req, res, next) => {
next(new NotFoundError('This resource was not found'));
});
// Error-handling middleware
app.use((err, req, res, next) => {
if (err instanceof NotFoundError) {
res.status(err.status).json({ error: err.message });
} else {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
}
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
NotFoundError
is created.NotFoundError
and handles it accordingly.Output: Navigating to /not-found
results in a 404 status response with { "error": "This resource was not found" }
.
Let’s build a more comprehensive example that includes:
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
// Middleware
app.use(bodyParser.json());
// Custom error classes
class ValidationError extends Error {
constructor(message) {
super(message);
this.status = 400;
}
}
class AuthenticationError extends Error {
constructor(message) {
super(message);
this.status = 401;
}
}
// Routes
app.post('/login', (req, res, next) => {
const { username, password } = req.body;
if (!username || !password) {
return next(new ValidationError('Username and password are required'));
}
if (username !== 'admin' || password !== 'password') {
return next(new AuthenticationError('Invalid credentials'));
}
res.send({ token: 'mysecrettoken' });
});
app.get('/protected', (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || authHeader !== 'Bearer mysecrettoken') {
return next(new AuthenticationError('Unauthorized access'));
}
res.send('Protected resource');
});
// Error-handling middleware
app.use((err, req, res, next) => {
if (err instanceof ValidationError) {
res.status(err.status).json({ error: err.message });
} else if (err instanceof AuthenticationError) {
res.status(err.status).json({ error: err.message });
} else {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
}
});
// Start the server
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
ValidationError
and AuthenticationError
are created.next
to pass errors to the error-handling middleware./login
with missing or incorrect credentials results in a 400 or 401 status response with an appropriate error message./protected
without proper authorization results in a 401 status response with “Unauthorized access”.Error handling in Express.js is essential for building robust and maintainable applications. By using error-handling middleware, you can catch and respond to errors effectively, ensuring a smooth user experience and easier debugging. This chapter covered basic and advanced error-handling techniques, providing a comprehensive guide to implementing error handling in your Express.js applications. Happy coding !❤️