This content originally appeared on DEV Community and was authored by shruti jain
When building backend applications with Express.js, handling errors properly is crucial. It not only helps you debug issues quickly but also ensures a better experience for users when something goes wrong.
In this blog, we’ll walk through how error handling works in Express, why it matters, and how you can implement it in your own projects.
What is Error Handling?
Error handling is the process of catching and responding to errors in your application. These errors could be anything from a missing file, a bad request, a database failure, or even a typo in your code. Instead of letting the app crash or return confusing messages, we use error handlers to catch these issues and return useful responses.
How Express Handles Errors
Express has a special kind of middleware just for errors. This middleware function must have four arguments: err
, req
, res
, and next
. Express automatically knows it’s an error handler because of the first err
parameter.
Here’s a basic error handler:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ message: 'Something went wrong!' });
});
You should add this at the end of all your route definitions so it can catch any errors from above.
Throwing and Catching Errors
1. Throwing Errors in Routes
You can directly throw an error in a route:
app.get('/', (req, res) => {
throw new Error('Server crashed');
});
Express will send this error to the error-handling middleware automatically.
2. Using next()
to Pass Errors
Instead of throwing, you can also pass the error using next()
:
app.get('/manual', (req, res, next) => {
const err = new Error('Something went wrong manually');
err.status = 400;
next(err);
});
Then in your error handler, you can use err.status
to return the correct status code.
Handling Errors in Async Code
Express doesn’t automatically catch errors in async functions. You need to use a try/catch
block or write a helper to manage this.
Option 1: try/catch
app.get('/async', async (req, res, next) => {
try {
const data = await someAsyncFunction();
res.send(data);
} catch (err) {
next(err);
}
});
Option 2: Wrapper Function
You can avoid repeating try/catch
by using a helper:
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.get('/safe', asyncHandler(async (req, res) => {
const data = await someAsyncFunction();
res.send(data);
}));
Handling 404 (Route Not Found)
If a user tries to access a route that doesn’t exist, you can handle it like this:
app.use((req, res) => {
res.status(404).json({ message: 'Route not found' });
});
This should be added just before the final error handler.
Full Example Structure
Here’s how your basic Express app might look with proper error handling:
const express = require('express');
const app = express();
// Routes
app.get('/', (req, res) => {
throw new Error('Home error');
});
// 404 Handler
app.use((req, res) => {
res.status(404).json({ message: 'Not Found' });
});
// Error Handler
app.use((err, req, res, next) => {
const status = err.statusCode || 500;
res.status(status).json({ message: err.message || 'Internal Server Error' });
});
app.listen(3000, () => console.log('Server is running on port 3000'));
Conclusion
Good error handling in Express.js makes your application more reliable and easier to maintain. By using middleware properly and planning for both sync and async errors, you can build a backend that handles problems gracefully without confusing your users or crashing your server.
If you’re just starting out with Express, try adding error handlers to your projects today and see how it improves your debugging and stability.
This content originally appeared on DEV Community and was authored by shruti jain