Skip to main content
Practice Problems

How to handle errors in Express.js?

Error Handling in Express.js

Express has a built-in error-handling mechanism. Proper error handling is essential to avoid crashes and return meaningful responses to clients.


Synchronous Errors

Express automatically catches errors thrown in synchronous route handlers:

js
app.get('/user/:id', (req, res) => { if (isNaN(req.params.id)) { throw new Error('ID must be a number'); // Express catches this } res.json({ id: req.params.id }); });

Asynchronous Errors

For async code, you must either use try/catch or pass errors to next():

js
// Option 1: try/catch + next(err) app.get('/users/:id', async (req, res, next) => { try { const user = await User.findById(req.params.id); if (!user) return res.status(404).json({ error: 'Not found' }); res.json(user); } catch (err) { next(err); // pass to error handler } }); // Option 2: asyncHandler wrapper const asyncHandler = fn => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next); app.get('/users/:id', asyncHandler(async (req, res) => { const user = await User.findById(req.params.id); if (!user) return res.status(404).json({ error: 'Not found' }); res.json(user); }));

Express 5 (currently in RC) handles async errors automatically.


Error-Handling Middleware

The error handler has 4 parameters — Express identifies it by the err argument:

js
// Must be registered LAST, after all routes app.use((err, req, res, next) => { console.error(err.stack); const status = err.statusCode || err.status || 500; const message = err.message || 'Internal Server Error'; res.status(status).json({ success: false, error: message, // only show stack in development ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) }); });

Custom Error Classes

js
class AppError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; this.isOperational = true; Error.captureStackTrace(this, this.constructor); } } class NotFoundError extends AppError { constructor(resource = 'Resource') { super(`${resource} not found`, 404); } } class ValidationError extends AppError { constructor(message) { super(message, 400); } } class UnauthorizedError extends AppError { constructor() { super('Unauthorized', 401); } } // Usage in routes app.get('/users/:id', asyncHandler(async (req, res) => { const user = await User.findById(req.params.id); if (!user) throw new NotFoundError('User'); res.json(user); }));

404 Handler

Catch unmatched routes — register it after all routes but BEFORE the error handler:

js
// After all routes app.use((req, res, next) => { next(new NotFoundError(`Route ${req.method} ${req.path}`)); }); // Error handler (last) app.use((err, req, res, next) => { res.status(err.statusCode || 500).json({ error: err.message }); });

Full Error Handling Setup

js
const express = require('express'); const app = express(); app.use(express.json()); // Routes app.use('/api/users', usersRouter); // 404 — must come after all routes app.use((req, res, next) => { res.status(404).json({ error: 'Route not found' }); }); // Global error handler — must be last app.use((err, req, res, next) => { const status = err.statusCode || 500; res.status(status).json({ error: err.message, ...(process.env.NODE_ENV !== 'production' && { stack: err.stack }) }); }); app.listen(3000);

Summary

Register error handlers with 4 parameters and place them last in the middleware chain. Use next(err) to forward errors from async code, and create custom error classes to carry HTTP status codes and contextual messages.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems