Install
$ agentstack add skill-versoxbt-claude-initial-setup-express-error-handling ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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
constructor(details: Record) {
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
): 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
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', ...)
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: VersoXBT
- Source: VersoXBT/claude-initial-setup
- License: MIT
- Homepage: https://github.com/VersoXBT/claude-initial-setup#installation
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.