| name | debug:express |
| description | Debug Express.js and Node.js applications with systematic diagnostic techniques. This skill provides comprehensive guidance for troubleshooting middleware execution issues, routing problems, CORS errors, async error handling, memory leaks, and unhandled promise rejections. Covers DEBUG environment variable usage, Node Inspector with Chrome DevTools, VS Code debugging, Morgan request logging, and diagnostic middleware patterns. Includes four-phase debugging methodology and common error message reference. |
Express.js Debugging Guide
A systematic approach to debugging Express.js applications using proven techniques and tools.
Common Error Patterns
1. Cannot GET /route (404 Errors)
Symptoms: Route returns 404, middleware not matching
Common Causes:
- Route not registered before catch-all handlers
- Missing leading slash in path
- Case sensitivity issues
- Router not mounted correctly
app.use('*', notFoundHandler);
app.get('/api/users', getUsers);
app.get('/api/users', getUsers);
app.use('*', notFoundHandler);
2. Middleware Not Executing
Symptoms: Request hangs, next() not called, order issues
Common Causes:
- Forgetting to call
next()
- Async middleware without proper error handling
- Wrong middleware order
app.use((req, res, next) => {
console.log('Request received');
});
app.use((req, res, next) => {
console.log('Request received');
next();
});
app.use(async (req, res, next) => {
try {
await someAsyncOperation();
next();
} catch (err) {
next(err);
}
});
3. CORS Errors
Symptoms: Browser blocks requests, preflight fails
Common Causes:
- CORS middleware placed after routes
- Missing OPTIONS handler
- Credentials not configured
const cors = require('cors');
app.get('/api/data', handler);
app.use(cors());
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);
4. Async Error Handling
Symptoms: Unhandled promise rejections, app crashes
Common Causes:
- Missing try/catch in async handlers
- Promises not caught
- No global error handler
app.get('/users', async (req, res) => {
const users = await User.findAll();
res.json(users);
});
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);
}));
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
});
});
5. Memory Leaks
Symptoms: Heap growing, OOM errors, slow responses over time
Common Causes:
- Unclosed database connections
- Event listeners not removed
- Large objects in closures
- Global caches without limits
const cache = {};
app.get('/data/:id', (req, res) => {
cache[req.params.id] = largeObject;
});
const LRU = require('lru-cache');
const cache = new LRU({ max: 500, ttl: 1000 * 60 * 5 });
node --inspect --expose-gc app.js
6. Unhandled Promise Rejections
Symptoms: Warnings in console, silent failures
Setup global handlers:
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
process.exit(1);
});
Debugging Tools
1. DEBUG Environment Variable
The most powerful built-in debugging tool for Express.
DEBUG=express:* node app.js
DEBUG=express:router node app.js
DEBUG=express:application,express:router node app.js
DEBUG=express:*,body-parser:* node app.js
DEBUG=myapp:* node app.js
const debug = require('debug')('myapp:server');
debug('Server starting on port %d', port);
2. Node Inspector (--inspect)
Start with Chrome DevTools support:
node --inspect app.js
node --inspect-brk app.js
node --inspect=0.0.0.0:9229 app.js
Open chrome://inspect in Chrome, click "Open dedicated DevTools for Node".
3. VS Code Debugger
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
}
]
}
4. Morgan Logger
HTTP request logging middleware:
const morgan = require('morgan');
app.use(morgan('dev'));
app.use(morgan('combined'));
app.use(morgan(':method :url :status :response-time ms - :res[content-length]'));
const fs = require('fs');
const accessLogStream = fs.createWriteStream('./access.log', { flags: 'a' });
app.use(morgan('combined', { stream: accessLogStream }));
5. ndb Debugger
Enhanced debugging experience:
npm install -g ndb
ndb node app.js
Features: Better UI, async stack traces, blackbox scripts, profile recording.
6. ESLint for Prevention
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"
}
}
The Four Phases (Express-specific)
Phase 1: Reproduce and Isolate
- Get exact error message - Check terminal, browser console, network tab
- Identify the route - Which endpoint is failing?
- Check request details - Method, headers, body, query params
- Minimal reproduction - Can you trigger with curl/Postman?
curl -v http://localhost:3000/api/users
curl -X POST -H "Content-Type: application/json" \
-d '{"name":"test"}' http://localhost:3000/api/users
Phase 2: Gather Information
-
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
- Set breakpoint at route handler entry
- Step through middleware chain
- Inspect req/res objects
Phase 3: Analyze and Hypothesize
-
Check the stack trace - Follow the call stack from error
-
Verify assumptions
- Is the route registered?
- Is middleware in correct order?
- Are environment variables set?
- Is database connected?
-
Common culprits checklist:
Phase 4: Fix and Verify
- Make one change at a time
- Test the specific failing case
- Run full test suite
- Check for regressions
npm test
npm test -- --watch
Quick Reference Commands
Start Debugging Session
DEBUG=express:*,myapp:* node --inspect app.js
node --inspect-brk app.js
DEBUG=express:* nodemon --inspect app.js
Inspect Running Process
ps aux | grep node
node --expose-gc -e "console.log(process.memoryUsage())"
Test Endpoints
curl -v http://localhost:3000/api/endpoint
curl -X POST http://localhost:3000/api/endpoint \
-H "Content-Type: application/json" \
-d '{"key": "value"}'
curl -H "Authorization: Bearer TOKEN" http://localhost:3000/api/protected
curl -L http://localhost:3000/redirect
curl -I http://localhost:3000/api/endpoint
Check Middleware Stack
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}`);
}
});
Memory Debugging
node --max-old-space-size=4096 app.js
node --inspect app.js
node -e "setInterval(() => console.log(process.memoryUsage()), 1000)"
Log Analysis
tail -f app.log | grep ERROR
grep -o 'Error: [^,]*' app.log | sort | uniq -c | sort -rn
grep -E '[0-9]{4,}ms' access.log
Diagnostic Middleware Template
Add this to quickly diagnose issues:
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);
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();
};
Common Error Messages Reference
| 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() |
Sources