Most Node.js error handling is an afterthought — catch blocks that swallow errors and generic 500 responses. Production-quality error handling requires custom error classes, structured logging, global handlers, and integration with error tracking services like Sentry.
⚡ TL;DR: Custom AppError with code/statusCode/isOperational. asyncHandler wrapper for async routes. 4-param Express error middleware at end. unhandledRejection handler that exits. Structured JSON logging. Sentry for non-operational errors.
Custom error hierarchy — Node.js error handling
class AppError extends Error {
constructor(message, code, statusCode=500, isOperational=true) {
super(message);
this.code = code; // 'USER_NOT_FOUND'
this.statusCode = statusCode;
this.isOperational = isOperational;
Error.captureStackTrace(this, this.constructor);
}
}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} ${id} not found`, 'NOT_FOUND', 404);
}
}
class ValidationError extends AppError {
constructor(msg, fields={}) {
super(msg, 'VALIDATION_FAILED', 422);
this.fields = fields;
}
}asyncHandler — no try/catch in routes
const asyncHandler = fn => (req,res,next) =>
Promise.resolve(fn(req,res,next)).catch(next);
app.get('/users/:id', asyncHandler(async (req,res) => {
const user = await UserService.findById(req.params.id);
if(!user) throw new NotFoundError('User', req.params.id);
res.json(user);
}));Express error middleware
// MUST have 4 params — tells Express it's error handler
app.use((err, req, res, next) => {
logger.error('Request error', {
err: {code:err.code, message:err.message},
req: {method:req.method, url:req.url, userId:req.user?.id}
});
if(!err.isOperational) Sentry.captureException(err);
res.status(err.statusCode||500).json({
error: {
code: err.code||'INTERNAL_ERROR',
message: err.isOperational ? err.message : 'Something went wrong'
}
});
});Global handlers
process.on('unhandledRejection',(reason)=>{
logger.error('Unhandled rejection',{reason});
Sentry.captureException(reason);
setTimeout(()=>process.exit(1),1000);
});
process.on('uncaughtException',(error)=>{
logger.error('Uncaught exception',{error});
Sentry.captureException(error);
setTimeout(()=>process.exit(1),1000);
});
process.on('SIGTERM',async()=>{
server.close(async()=>{ await db.end(); process.exit(0); });
setTimeout(()=>process.exit(1),30000);
});- ✅ Custom error classes with code, statusCode, isOperational
- ✅ asyncHandler — single wrapper instead of try/catch in every route
- ✅ 4-param error middleware at end of Express app
- ✅ Global unhandledRejection + uncaughtException handlers
- ✅ Graceful shutdown on SIGTERM
- ❌ Never swallow errors in empty catch blocks
- ❌ Never expose stack traces in production responses
Error handling pairs with API security — never expose internals. External reference: Joyent Node.js error guide.
Recommended Reading
→ Designing Data-Intensive Applications — The bible of distributed systems and production engineering at scale.
→ The Pragmatic Programmer — Timeless engineering wisdom every senior developer needs.
Affiliate links. We earn a small commission at no extra cost to you.
Free Weekly Newsletter
🚀 Join 2,000+ Senior Developers
Get expert-level JavaScript, Python, AWS, system design and AI secrets every week. Zero fluff, pure signal.
Related: Node.js Error Handling: From try/catch to Production Error Monitoring · Express.js Best Practices: Build Production-Ready APIs With Node.js · Node.js Security Best Practices: Protect Your API From the Most Common Attacks
Related: Lambda Timeouts, Retries, and DLQs: Idempotent Failure Handling · Node.js Async Hooks: The Tracing API That Powers Every APM Tool · Python Context Managers: Master the with Statement for Clean Resource Handling
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Agentic Git Workflows: Atomic Commits From Noisy LLM Diffs
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
Newly added
- Amazon Bedrock Model Evaluation: Score Coding-Agent Outputs Before You Promote a Prompt
- AWS App Runner: Host Coding-Agent HTTP APIs Without Babysitting Containers
- Amazon S3 Object Lock: Immutable Artifact Buckets Coding Agents Cannot Overwrite
- AWS Security Hub: Aggregate Coding-Agent Security Findings Across Accounts
- Amazon Detective: Investigate Compromised Coding-Agent Credentials After GuardDuty
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
