Express error handling
Skill VersoXBT/claude-initial-setup/skills/express-node/express-error-handling
Express.js error handling patterns including async error catching, custom error classes, centralized error middleware, and the distinction between operational and programmer errors. Use when the user is handling errors in Express, writing async route handlers, creating custom error types, or debugging unhandled rejections. Trigger on mentions of Express error handling, async errors, error middleware, AppError, or unhandled exceptions in Node.js.From its SKILL.md
npx -y skills add VersoXBT/claude-initial-setup --skill express-error-handlingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
6.4 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Express Error Handling
Patterns for comprehensive, centralized error handling in Express.js applications.
When to Use
- User is writing Express route handlers with async operations
- User needs custom error classes for different HTTP status codes
- User asks about centralized error handling
- User has unhandled promise rejections or uncaught exceptions
- User needs to distinguish between operational and programmer errors
Core Patterns
Custom Error Classes
Create a hierarchy of application errors that carry HTTP status codes and operational flags.
export class AppError extends Error {
readonly statusCode: number
readonly isOperational: boolean
constructor(message: string, statusCode: number, isOperational = true) {
super(message)
this.statusCode = statusCode
this.isOperational = isOperational
Object.setPrototypeOf(this, new.target.prototype)
}
}
export class NotFoundError extends AppError {
constructor(resource: string) {
super(`${resource} not found`, 404)
}
}
export class ValidationError extends AppError {
readonly details: Record<string, string[]>
constructor(details: Record<string, string[]>) {
super('Validation failed', 400)
this.details = details
}
}
export class UnauthorizedError extends AppError {
constructor(message = 'Authentication required') {
super(message, 401)
}
}
export class ForbiddenError extends AppError {
constructor(message = 'Insufficient permissions') {
super(message, 403)
}
}
export class ConflictError extends AppError {
constructor(message: string) {
super(message, 409)
}
}
Async Error Wrapper
Express does not catch errors from async route handlers automatically. Wrap them to forward errors to the error middleware.
import { Request, Response, NextFunction, RequestHandler } from 'express'
function asyncHandler(
fn: (req: Request, res: Response, next: NextFunction) => Promise<void>
): RequestHandler {
return (req, res, next) => {
fn(req, res, next).catch(next)
}
}
// Usage -- errors are automatically forwarded to error middleware
router.get(
'/users/:id',
asyncHandler(async (req, res) => {
const user = await db.user.findUnique({ where: { id: req.params.id } })
if (!user) throw new NotFoundError('User')
res.json({ data: user })
})
)
Centralized Error Middleware
A single error handler that formats all errors consistently. Must have exactly 4 parameters.
import { Request, Response, NextFunction } from 'express'
import { AppError, ValidationError } from './errors'
interface ErrorResponse {
error: string
details?: Record<string, string[]>
stack?: string
}
function errorHandler(err: Error, req: Request, res: Response, _next: NextFunction): void {
if (err instanceof ValidationError) {
const body: ErrorResponse = { error: err.message, details: err.details }
res.status(err.statusCode).json(body)
return
}
if (err instanceof AppError) {
const body: ErrorResponse = { error: err.message }
res.status(err.statusCode).json(body)
return
}
// Programmer error -- do not leak internals
console.error('Unexpected error:', err)
const body: ErrorResponse = { error: 'Internal server error' }
if (process.env.NODE_ENV === 'development') {
body.stack = err.stack
}
res.status(500).json(body)
}
export { errorHandler }
Operational vs Programmer Errors
Operational errors are expected (invalid input, resource not found, network timeout). Programmer errors are bugs (TypeError, undefined access). Handle them differently.
// Operational -- expected, recoverable
throw new NotFoundError('User')
throw new ValidationError({ email: ['Invalid email format'] })
// Programmer -- unexpected, indicates a bug
// These should crash the process in production (after cleanup)
const user = undefined
user.name // TypeError -- programmer error
// In production, catch unhandled errors and restart gracefully
process.on('uncaughtException', (err) => {
console.error('UNCAUGHT EXCEPTION -- shutting down:', err)
server.close(() => process.exit(1))
})
process.on('unhandledRejection', (reason) => {
console.error('UNHANDLED REJECTION -- shutting down:', reason)
server.close(() => process.exit(1))
})
404 Handler
Catch requests that do not match any route. Register after all routes but before the error handler.
function notFoundHandler(req: Request, res: Response, _next: NextFunction): void {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
})
}
// Registration order
app.use('/api', apiRoutes)
app.use(notFoundHandler) // After routes
app.use(errorHandler) // After 404
Anti-Patterns
- try/catch in every single route handler -- Use the asyncHandler wrapper instead. Centralize error formatting in the error middleware, not in each handler.
- Sending error response AND calling next(err) -- This causes "headers already sent" errors. Do one or the other, never both.
- Swallowing errors silently --
catch () {}hides bugs. Always log unexpected errors and forward them to the error handler. - Leaking stack traces in production -- Never send
err.stackor internal details in production responses. Attackers use these to find vulnerabilities. - Treating all errors the same -- A validation error (400) and a database connection failure (500) need different handling. Use the AppError hierarchy to distinguish them.
Quick Reference
Error hierarchy:
AppError (base)
NotFoundError (404)
ValidationError (400)
UnauthorizedError (401)
ForbiddenError (403)
ConflictError (409)
Middleware registration order:
1. Routes
2. 404 handler (3 params)
3. Error handler (4 params)
Async pattern:
router.get('/path', asyncHandler(async (req, res) => { ... }))
Process-level:
process.on('uncaughtException', ...)
process.on('unhandledRejection', ...)
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most error diagnosis skills give in ~1.3k tokens
Counted across 135 of the 162 authors here whose files we hold, read 2026-09-06
- Handle, re-throw, or log in every catch blockin 12 of 135, across 7 files
- Use typed error classes over string messagesin 11 of 135, across 6 files
- Log full error context server-sidein 10 of 135, across 5 files
- Document every error code clients may receivein 9 of 135, across 4 files
- Surface errors at the boundary where they occurin 9 of 135, across 4 files
- Wrap React components in an ErrorBoundaryin 9 of 135, across 4 files
- Wrap errors with context, never lose the originalin 9 of 135, across 4 files
- Use the standard error envelope for API responsesin 9 of 135, across 4 files
- Retry only retriable errors, never 4xx client errorsin 8 of 135, across 3 files
- Retry transient failures with exponential backoff and jitterin 8 of 135
- Show users friendly messages without technical detailsin 7 of 135, across 3 files
- Use the Result pattern for expected failuresin 7 of 135, across 5 files
Said here and by no other author read
- Wrap async route handlers to forward errors
- Create custom error classes carrying HTTP status codes
- Use a single centralized error middleware
- Give the error handler exactly four parameters
- Distinguish operational errors from programmer errors
- Register the 404 handler after all routes
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.