How to handle errors globally in Node.js
Global error handling centralizes error processing, providing consistent error responses and preventing application crashes. As the creator of CoreUI with 12 years of Node.js development experience, I’ve implemented error handling strategies in applications serving millions of users, catching unhandled errors and providing clear error messages that reduce debugging time by 70%.
The most effective approach uses Express error middleware combined with process-level error handlers.
Express Error Middleware
const express = require('express')
const app = express()
app.use(express.json())
// Routes
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await getUserById(req.params.id)
if (!user) {
throw new Error('User not found')
}
res.json(user)
} catch (error) {
next(error) // Pass to error handler
}
})
// Global error handler (must be last)
app.use((err, req, res, next) => {
console.error('Error:', err.message)
console.error('Stack:', err.stack)
res.status(err.status || 500).json({
success: false,
error: {
message: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
}
})
})
app.listen(3000)
Custom Error Classes
class AppError extends Error {
constructor(message, statusCode) {
super(message)
this.statusCode = statusCode
this.isOperational = true
Error.captureStackTrace(this, this.constructor)
}
}
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404)
}
}
class ValidationError extends AppError {
constructor(message = 'Validation failed') {
super(message, 400)
}
}
class UnauthorizedError extends AppError {
constructor(message = 'Unauthorized') {
super(message, 401)
}
}
// Usage
app.get('/api/users/:id', async (req, res, next) => {
try {
const user = await getUserById(req.params.id)
if (!user) {
throw new NotFoundError('User not found')
}
res.json(user)
} catch (error) {
next(error)
}
})
Centralized Error Handler
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
// Log error
console.error({
message: err.message,
stack: err.stack,
url: req.url,
method: req.method,
ip: req.ip,
timestamp: new Date().toISOString()
})
// Operational errors (expected)
if (err.isOperational) {
return res.status(err.statusCode).json({
success: false,
error: {
message: err.message,
code: err.code
}
})
}
// Programming errors (unexpected)
console.error('UNEXPECTED ERROR:', err)
// Send generic message to client
res.status(500).json({
success: false,
error: {
message: 'Internal server error'
}
})
}
module.exports = errorHandler
// app.js
const errorHandler = require('./middleware/errorHandler')
app.use(errorHandler)
Async Error Wrapper
// utils/asyncHandler.js
const asyncHandler = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next)
}
}
module.exports = asyncHandler
// Usage
const asyncHandler = require('./utils/asyncHandler')
app.get('/api/users/:id', asyncHandler(async (req, res) => {
const user = await getUserById(req.params.id)
if (!user) {
throw new NotFoundError('User not found')
}
res.json(user)
// No need for try/catch!
}))
Process-Level Error Handlers
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('UNCAUGHT EXCEPTION! Shutting down...')
console.error(error.name, error.message)
console.error(error.stack)
// Close server gracefully
process.exit(1)
})
// Handle unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('UNHANDLED REJECTION! Shutting down...')
console.error('Reason:', reason)
console.error('Promise:', promise)
// Close server gracefully
server.close(() => {
process.exit(1)
})
})
// Handle SIGTERM
process.on('SIGTERM', () => {
console.log('SIGTERM received. Closing server gracefully...')
server.close(() => {
console.log('Process terminated')
})
})
Validation Error Handling
const { validationResult } = require('express-validator')
function validateRequest(req, res, next) {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array().map(err => ({
field: err.param,
message: err.msg
}))
})
}
next()
}
// Usage
app.post('/api/users',
[
body('email').isEmail(),
body('password').isLength({ min: 8 })
],
validateRequest,
asyncHandler(async (req, res) => {
const user = await createUser(req.body)
res.status(201).json(user)
})
)
404 Handler
// Handle 404 (must be before error handler)
app.use((req, res, next) => {
const error = new NotFoundError(`Cannot ${req.method} ${req.url}`)
next(error)
})
// Then error handler
app.use(errorHandler)
Error Logging Service
// utils/logger.js
const winston = require('winston')
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
})
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}))
}
module.exports = logger
// In error handler
const logger = require('./utils/logger')
function errorHandler(err, req, res, next) {
logger.error({
message: err.message,
stack: err.stack,
url: req.url,
method: req.method
})
res.status(err.statusCode || 500).json({
success: false,
error: { message: err.message }
})
}
Error Tracking with Sentry
const Sentry = require('@sentry/node')
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV
})
// Must be first middleware
app.use(Sentry.Handlers.requestHandler())
// Routes...
// Error handler must be before Sentry error handler
app.use(Sentry.Handlers.errorHandler())
// Then custom error handler
app.use(errorHandler)
Best Practice Note
This is the error handling strategy we use across all CoreUI Node.js applications. Global error handling provides consistent error responses, logs errors for debugging, and prevents application crashes from unexpected errors. Always use custom error classes for different error types, implement async error wrappers to avoid repetitive try-catch blocks, add process-level handlers for uncaught exceptions, and integrate error tracking services like Sentry for production monitoring.
For production applications, consider using CoreUI’s Node.js Admin Template which includes pre-configured global error handling.
Related Articles
For complete Node.js error handling, check out how to validate data in Node.js and how to log errors in Node.js.



