Debug Express.js and Node.js applications with systematic diagnostic techniques...
A systematic approach to debugging Express.js applications using proven techniques and tools.
Symptoms: Route returns 404, middleware not matching Common Causes:
// Wrong: catch-all before specific routes
app.use('*', notFoundHandler);
app.get('/api/users', getUsers); // Never reached
// Correct: specific routes before catch-all
app.get('/api/users', getUsers);
app.use('*', notFoundHandler);
Symptoms: Request hangs, next() not called, order issues Common Causes:
next()// Wrong: missing next()
app.use((req, res, next) => {
console.log('Request received');
// Hangs - next() never called
});
// Correct: always call next() or send response
app.use((req, res, next) => {
console.log('Request received');
next();
});
// Correct async middleware
app.use(async (req, res, next) => {
try {
await someAsyncOperation();
next();
} catch (err) {
next(err); // Pass error to error handler
}
});
Symptoms: Browser blocks requests, preflight fails Common Causes:
const cors = require('cors');
// Wrong: CORS after routes
app.get('/api/data', handler);
app.use(cors()); // Too late
// Correct: CORS before routes
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
app.get('/api/data', handler);
Symptoms: Unhandled promise rejections, app crashes Common Causes:
// Wrong: unhandled async error
app.get('/users', async (req, res) => {
const users = await User.findAll(); // Throws, crashes app
res.json(users);
});
// Correct: wrap async handlers
const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
app.get('/users', asyncHandler(async (req, res) => {
const users = await User.findAll();
res.json(users);
}));
// Global error handler (must be last)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message
});
});
Symptoms: Heap growing, OOM errors, slow responses over time Common Causes:
// Wrong: unbounded cache
const cache = {};
app.get('/data/:id', (req, res) => {
cache[req.params.id] = largeObject; // Memory leak
});
// Correct: use LRU cache with limits
const LRU = require('lru-cache');
const cache = new LRU({ max: 500, ttl: 1000 * 60 * 5 });
// Check for leaks
node --inspect --expose-gc app.js
// Use Chrome DevTools Memory tab
Symptoms: Warnings in console, silent failures Setup global handlers:
// Add to app entry point
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// Log to error tracking service
});
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
// Graceful shutdown
process.exit(1);
});
The most powerful built-in debugging tool for Express.
# See all Express internal logs
DEBUG=express:* node app.js
# Specific areas only
DEBUG=express:router node app.js
DEBUG=express:application,express:router node app.js
# Multiple packages
DEBUG=express:*,body-parser:* node app.js
# Your own debug statements
DEBUG=myapp:* node app.js
// In your code
const debug = require('debug')('myapp:server');
debug('Server starting on port %d', port);
Start with Chrome DevTools support:
# Start with inspector
node --inspect app.js
# Break on first line
node --inspect-brk app.js
# Specific port
node --inspect=0.0.0.0:9229 app.js
Open chrome://inspect in Chrome, click "Open dedicated DevTools for Node".
Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Express",
"program": "${workspaceFolder}/app.js",
"env": {
"DEBUG": "express:*",
"NODE_ENV": "development"
},
"console": "integratedTerminal"
},
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 9229
}
]
}
HTTP request logging middleware:
const morgan = require('morgan');
// Development: colored, concise
app.use(morgan('dev'));
// Production: Apache combined format
app.use(morgan('combined'));
// Custom format with response time
app.use(morgan(':method :url :status :response-time ms - :res[content-length]'));
// Log to file
const fs = require('fs');
const accessLogStream = fs.createWriteStream('./access.log', { flags: 'a' });
app.use(morgan('combined', { stream: accessLogStream }));
Enhanced debugging experience:
npm install -g ndb
ndb node app.js
Features: Better UI, async stack traces, blackbox scripts, profile recording.
Catch errors before runtime:
npm install eslint eslint-plugin-node --save-dev
npx eslint --init
{
"extends": ["eslint:recommended", "plugin:node/recommended"],
"rules": {
"no-unused-vars": "error",
"no-undef": "error",
"node/no-missing-require": "error"
}
}
# Test endpoint directly
curl -v http://localhost:3000/api/users
curl -X POST -H "Content-Type: application/json" \
-d '{"name":"test"}' http://localhost:3000/api/users
Enable DEBUG logging
DEBUG=express:* node app.js
Add strategic logging
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
console.log('Headers:', req.headers);
console.log('Body:', req.body);
next();
});
Check middleware order
app._router.stack.forEach((r, i) => {
if (r.route) {
console.log(`${i}: Route ${r.route.path}`);
} else if (r.name) {
console.log(`${i}: Middleware ${r.name}`);
}
});
Inspect with breakpoints
Check the stack trace - Follow the call stack from error
Verify assumptions
Common culprits checklist:
# Run tests
npm test
# Watch mode during fixes
npm test -- --watch
# Full debug output
DEBUG=express:*,myapp:* node --inspect app.js
# Attach debugger and break immediately
node --inspect-brk app.js
# With nodemon for auto-reload
DEBUG=express:* nodemon --inspect app.js
# List Node processes
ps aux | grep node
# Attach Chrome DevTools
# Open chrome://inspect in browser
# Memory usage
node --expose-gc -e "console.log(process.memoryUsage())"
# GET request with verbose output
curl -v http://localhost:3000/api/endpoint
# POST with JSON
curl -X POST http://localhost:3000/api/endpoint \
-H "Content-Type: application/json" \
-d '{"key": "value"}'
# With authorization
curl -H "Authorization: Bearer TOKEN" http://localhost:3000/api/protected
# Follow redirects
curl -L http://localhost:3000/redirect
# Show response headers
curl -I http://localhost:3000/api/endpoint
// Add to app.js temporarily
console.log('Middleware stack:');
app._router.stack.forEach((layer, index) => {
if (layer.route) {
console.log(`${index}: Route - ${Object.keys(layer.route.methods)} ${layer.route.path}`);
} else if (layer.name === 'router') {
console.log(`${index}: Router - ${layer.regexp}`);
} else {
console.log(`${index}: Middleware - ${layer.name}`);
}
});
# Start with increased memory
node --max-old-space-size=4096 app.js
# Generate heap snapshot
node --inspect app.js
# In Chrome DevTools: Memory tab > Take heap snapshot
# Track memory over time
node -e "setInterval(() => console.log(process.memoryUsage()), 1000)"
# Tail logs with filtering
tail -f app.log | grep ERROR
# Count error types
grep -o 'Error: [^,]*' app.log | sort | uniq -c | sort -rn
# Find slow requests (Morgan format)
grep -E '[0-9]{4,}ms' access.log
Add this to quickly diagnose issues:
// debug-middleware.js
const debug = require('debug')('myapp:debug');
module.exports = function diagnosticMiddleware(req, res, next) {
const start = Date.now();
debug('Incoming request:');
debug(' Method: %s', req.method);
debug(' URL: %s', req.originalUrl);
debug(' Headers: %O', req.headers);
debug(' Body: %O', req.body);
debug(' Query: %O', req.query);
debug(' Params: %O', req.params);
// Capture response
const originalSend = res.send;
res.send = function(body) {
const duration = Date.now() - start;
debug('Response:');
debug(' Status: %d', res.statusCode);
debug(' Duration: %dms', duration);
debug(' Body length: %d', body?.length || 0);
return originalSend.call(this, body);
};
next();
};
// Usage: app.use(require('./debug-middleware'));
| Error | Cause | Solution |
|---|---|---|
Cannot GET /path |
Route not found | Check route registration, order |
TypeError: Cannot read property 'x' of undefined |
Missing data in req | Validate req.body, req.params |
Error: Request timeout |
Slow operation, no response | Check DB, add timeout handling |
PayloadTooLargeError |
Body exceeds limit | Increase body-parser limit |
ECONNREFUSED |
Can't connect to service | Check DB/Redis is running |
EADDRINUSE |
Port already in use | Kill process or change port |
ERR_HTTP_HEADERS_SENT |
Response sent twice | Remove duplicate res.send() |