Framework Guide
Express API Monitoring
Set up health check endpoints and uptime monitoring for your Express.js application. Production-ready patterns for API monitoring.
Why Monitor Your Express App?
Express.js is the most popular Node.js web framework, powering millions of APIs in production. But its minimalist design means there is no built-in health monitoring -- it is up to you to detect when your app silently stops working. External monitoring catches failures that process managers and internal logging miss.
- Unhandled exceptions -- A single uncaught error can crash the Express process, leaving users with connection refused errors until PM2 restarts it
- Middleware chain failures -- A broken middleware can silently swallow requests without sending a response, causing timeouts
- Memory leaks -- Express apps often accumulate memory through closures, event listeners, or cached data until the process is killed
- Dependency outages -- Database, Redis, or external API failures cause cascading 500 errors across your routes
Simple Health Endpoint
const express = require('express');
const app = express();
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString()
});
});
Comprehensive Health Check
const express = require('express');
const app = express();
app.get('/health', (req, res) => {
res.json({ status: 'healthy' });
});
app.get('/ready', async (req, res) => {
const checks = {};
let healthy = true;
// Check database
try {
const start = Date.now();
await db.query('SELECT 1');
checks.database = {
status: 'ok',
responseTimeMs: Date.now() - start
};
} catch (error) {
healthy = false;
checks.database = {
status: 'error',
message: error.message
};
}
// Check Redis
try {
const start = Date.now();
await redis.ping();
checks.redis = {
status: 'ok',
responseTimeMs: Date.now() - start
};
} catch (error) {
healthy = false;
checks.redis = {
status: 'error',
message: error.message
};
}
const statusCode = healthy ? 200 : 503;
res.status(statusCode).json({
status: healthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
checks,
uptime: process.uptime(),
memory: process.memoryUsage()
});
});
Health Check Router
// routes/health.js
const express = require('express');
const router = express.Router();
// Liveness probe
router.get('/live', (req, res) => {
res.json({ status: 'alive' });
});
// Readiness probe
router.get('/ready', async (req, res) => {
try {
await checkDependencies();
res.json({ status: 'ready' });
} catch (error) {
res.status(503).json({
status: 'not ready',
error: error.message
});
}
});
// Detailed health
router.get('/detailed', async (req, res) => {
const health = await getDetailedHealth();
res.status(health.healthy ? 200 : 503).json(health);
});
module.exports = router;
// app.js
const healthRouter = require('./routes/health');
app.use('/health', healthRouter);
Using express-healthcheck
const healthcheck = require('express-healthcheck');
app.use('/health', healthcheck({
healthy: function () {
return { everything: 'is ok' };
}
}));
// Custom checks
app.use('/health', healthcheck({
test: async function () {
// Throw error if unhealthy
await db.query('SELECT 1');
await redis.ping();
}
}));
Add Timeouts
async function checkWithTimeout(fn, timeoutMs = 5000) {
return Promise.race([
fn(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), timeoutMs)
)
]);
}
app.get('/ready', async (req, res) => {
try {
await checkWithTimeout(() => db.query('SELECT 1'), 5000);
await checkWithTimeout(() => redis.ping(), 2000);
res.json({ status: 'ready' });
} catch (error) {
res.status(503).json({
status: 'not ready',
error: error.message
});
}
});
Skip Logging for Health Checks
const morgan = require('morgan');
// Skip health check logging
app.use(morgan('combined', {
skip: (req, res) => req.path === '/health' || req.path === '/ready'
}));
Best Practices
- Separate liveness and readiness — /health/live and /health/ready serve different purposes
- Add timeouts — Don't let slow dependencies hang the health check
- Skip logging — Avoid cluttering logs with health check traffic
- Include process info — Uptime, memory usage, and version help debugging