Express Error Handling Typed Middleware
Centralize Express error handling in TypeScript with a typed AppError hierarchy, status mapping, and an exhaustively narrowed middleware.
Code
import express, { NextFunction, Request, Response } from 'express';
type ErrorKind = 'validation' | 'not-found' | 'conflict' | 'internal';
class AppError extends Error {
constructor(
readonly kind: ErrorKind,
message: string,
readonly status = 500,
) {
super(message);
}
}
const statusFor = (kind: ErrorKind): number => {
switch (kind) {
case 'validation': return 400;
case 'not-found': return 404;
case 'conflict': return 409;
case 'internal': return 500;
}
};
const wrap = (fn: (req: Request, res: Response) => Promise<unknown>) =>
(req: Request, res: Response, next: NextFunction) => {
fn(req, res).catch(next);
};
const app = express();
app.use(express.json());
app.get('/items/:id', wrap(async (req, res) => {
if (req.params.id === '0') {
throw new AppError('not-found', 'item 0 does not exist');
}
res.json({ id: req.params.id });
}));
app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
if (!(err instanceof AppError)) {
console.error('unexpected:', err);
res.status(500).json({ error: 'internal error' });
return;
}
const status = statusFor(err.kind);
if (status >= 500) console.error('server error:', err.message);
res.status(status).json({ error: err.message, kind: err.kind });
});
app.listen(3000);Report a problem with this exampleOpens a pre-filled issue on GitHub.