Error handling is a critical aspect of developing robust and reliable web applications. In Express.js, handling errors effectively ensures that your application can gracefully recover from unexpected issues and provide meaningful feedback to users. This chapter covers error handling in Express.js, from basic concepts to advanced techniques. We will explore different types of errors, how to catch and handle them, and best practices for creating a resilient application.
Bonus💡: Practical example at the end of article
Error handling involves detecting, managing, and responding to errors that occur during the execution of your application. Effective error handling ensures your application remains stable and provides useful feedback to users and developers.
There are several types of errors that can occur in an Express.js application:
The try-catch statement allows you to handle errors in synchronous code.
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
try {
// Simulate an error
throw new Error('Something went wrong!');
} catch (err) {
res.status(500).send('Internal Server Error');
}
});
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
Visiting http://localhost:3000
will display “Internal Server Error” due to the simulated error.
Express.js has a built-in error handler that catches and handles errors thrown in route handlers and middleware.
app.get('/', (req, res) => {
throw new Error('Something went wrong!');
});
Express.js will catch the error and respond with a 500 Internal Server Error.
Custom error handlers allow you to define specific behavior for different types of errors.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
A common requirement is to handle 404 Not Found errors.
app.use((req, res, next) => {
res.status(404).send('Sorry, we could not find that!');
});
Error handling middleware is a special type of middleware with four arguments: err
, req
, res
, and next
.
Example:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
The next()
function is used to pass control to the next middleware or error handler.
app.get('/', (req, res, next) => {
const err = new Error('Something went wrong!');
next(err);
});
The simplest way to log errors is to use console.error
.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
For more robust logging, integrate with logging libraries like Winston or Morgan.
const winston = require('winston');
const expressWinston = require('express-winston');
app.use(expressWinston.errorLogger({
transports: [
new winston.transports.Console()
]
}));
Use try-catch
with async
/await
to handle errors in asynchronous code.
app.get('/', async (req, res, next) => {
try {
await someAsyncFunction();
res.send('Hello, world!');
} catch (err) {
next(err);
}
});
Handle errors in promises using .catch
.
app.get('/', (req, res, next) => {
somePromiseFunction()
.then(result => {
res.send(result);
})
.catch(err => {
next(err);
});
});
Centralized error handling ensures that all errors are processed in a single place.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
let’s build a full practical example demonstrating error handling in an Express.js application. This example will include handling different types of errors, logging, and using custom error handlers.
error-handling-example/
├── views/
│ └── error.ejs
├── index.js
├── package.json
└── app.js
mkdir error-handling-example
cd error-handling-example
npm init -y
npm install express ejs winston
index.js
file:
const express = require('express');
const app = require('./app');
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
app.js
file:
const express = require('express');
const winston = require('winston');
const app = express();
// Setting up the view engine
app.set('view engine', 'ejs');
app.set('views', './views');
// Middleware to simulate a route with an error
app.get('/error', (req, res, next) => {
const err = new Error('Simulated error');
err.status = 500;
next(err);
});
// Middleware to simulate a 404 Not Found error
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
// Error handling middleware
app.use((err, req, res, next) => {
// Log the error using winston
winston.error(`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);
// Respond with a user-friendly message
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: process.env.NODE_ENV === 'development' ? err : {}
});
});
module.exports = app;
views/error.ejs
file:
Error
Error
<%= message %>
<% if (error.status) { %>
Status: <%= error.status %>
<% } %>
<% if (error.stack) { %>
<%= error.stack %>
<% } %>
We create a new Node.js project and install the necessary dependencies (express
, ejs
, and winston
).
index.js
):app.js
):/error
that throws a simulated error.views/error.ejs
):Start the server:
node index.js
Visit the following URLs in your browser:
/error
:
Error
Error
Simulated error
Status: 500
Error: Simulated error
at /path/to/your/project/app.js:10:12
...
/nonexistent
:
Error
Error
Not Found
Status: 404
Error handling is a crucial aspect of developing reliable and maintainable Express.js applications. By understanding the different types of errors, using custom error handlers, and following best practices, you can create robust applications that handle errors gracefully. Remember to log errors and use monitoring tools to keep track of issues in your application. Happy coding !❤️