Express
A terminal error-handling middleware that maps @gentleduck/error instances to JSON responses, with a fallback for everything else.
Overview
Express identifies error-handling middleware by arity: a handler with four parameters
(err, req, res, next), and dispatches to it whenever a route calls next(err) or throws
inside an async handler wrapped to forward its rejection. Register one at the end of the
middleware chain, after every route.
Full runnable example: examples/express.
Error middleware
import type { ErrorRequestHandler } from 'express'
import { AppError } from './errors'
export const errorHandler: ErrorRequestHandler = (err, _req, res, _next) => {
if (err instanceof AppError) {
res.status(err.status).json(err.toJSON())
return
}
// eslint-disable-next-line no-console
console.error(err)
res.status(500).json({ ok: false, error: { code: 'INTERNAL', status: 500 } })
}
import express from 'express'
import { errorHandler } from './error-handler'
import { widgetsRouter } from './widgets'
const app = express()
app.use('/widgets', widgetsRouter)
// Registered last: Express only treats a 4-arg handler as error middleware,
// and only routes reached after this point in the chain forward to it.
app.use(errorHandler)
app.listen(3000)
Throwing from a route
Express does not catch a rejected promise from an async handler on its own. Forward it to
next explicitly, or wrap the handler:
import { Router } from 'express'
import { throwAppError } from './errors'
export const widgetsRouter = Router()
widgetsRouter.get('/:id', async (req, res, next) => {
try {
const widget = await db.widgets.findById(req.params.id)
if (!widget) throwAppError('NOT_FOUND', { resource: 'widget', id: req.params.id })
res.json(widget)
} catch (err) {
next(err)
}
})
Express 5 forwards a rejected async handler's promise to
next automatically, which removes the
need for the try/catch above. Express 4 does not. Confirm which major version you're on
before dropping it.