express-error-handling — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited express-error-handling (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Patterns for comprehensive, centralized error handling in Express.js applications.
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)
}
}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 })
})
)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 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))
})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 404catch () {} hides bugs. Always log unexpected errors and forward them to the error handler.err.stack or internal details in production responses. Attackers use these to find vulnerabilities.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', ...)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.