Error handling is a critical part of any Express application. Express provides built-in mechanisms for handling both synchronous and asynchronous errors.
Error Handling Middleware
Error-handling middleware functions have four arguments instead of three: (err, req, res, next).
Basic Error Handler
Advanced Error Handler
function errorHandler ( err , req , res , next ) {
// Log the error
console . error ( err . stack );
// Send error response
res . status ( 500 );
res . send ( 'Internal Server Error' );
}
// Must be defined after all other routes
app . use ( errorHandler );
Error-handling middleware must be defined after all other app.use() and route calls. Otherwise, it won’t catch errors from those routes.
Catching Errors
Synchronous Errors
Express automatically catches synchronous errors thrown in route handlers: app . get ( '/' , function ( req , res ) {
// Express catches this automatically
throw new Error ( 'Something broke!' );
});
Asynchronous Errors
For errors in asynchronous code, pass them to next(): app . get ( '/async' , function ( req , res , next ) {
// Simulate async operation
process . nextTick ( function () {
next ( new Error ( 'Async error!' ));
});
});
Promise Rejections
For promises, catch rejections and pass to next(): app . get ( '/data' , function ( req , res , next ) {
fetchData ()
. then ( data => res . json ( data ))
. catch ( next ); // Pass errors to Express
});
Custom Error Types
Create custom errors with status codes:
app . get ( '/forbidden' , function ( req , res , next ) {
const err = new Error ( 'Not allowed!' );
err . status = 403 ;
next ( err );
});
404 Handler
Handle 404 errors by adding a catch-all middleware before your error handler:
// 404 handler - must be after all routes
app . use ( function ( req , res , next ) {
res . status ( 404 );
res . format ({
html : function () {
res . render ( '404' , { url: req . url });
},
json : function () {
res . json ({ error: 'Not found' });
},
default : function () {
res . type ( 'txt' ). send ( 'Not found' );
}
});
});
// Error handler - must be last
app . use ( function ( err , req , res , next ) {
res . status ( err . status || 500 );
res . render ( 'error' , { error: err });
});
Environment-Specific Handling
Disable verbose error details in production to avoid leaking sensitive information:
app . enable ( 'verbose errors' );
if ( app . get ( 'env' ) === 'production' ) {
app . disable ( 'verbose errors' );
}
app . use ( function ( err , req , res , next ) {
res . status ( err . status || 500 );
// Only show stack trace in development
res . render ( 'error' , {
message: err . message ,
error: app . get ( 'verbose errors' ) ? err : {}
});
});
Best Practices
Always use error-handling middleware
Define at least one error-handling middleware function to catch and handle errors gracefully.
In asynchronous code, always pass errors to next(err) rather than throwing them.
Set appropriate status codes
Use proper HTTP status codes (404, 403, 500, etc.) to indicate the type of error.
Always log errors for debugging, but don’t expose stack traces to users in production.