Express.js Health Check — Complete Implementation Guide
In this tutorial, you will learn about Express.js Health Check. We cover key concepts, practical examples, and best practices to help you master this topic.
Express.js health check endpoints integrate seamlessly with Kubernetes probes, load balancers, and monitoring systems by providing dedicated routes for liveness, readiness, and startup checks.
What You'll Learn
By the end of this tutorial, you will know how to implement health check routes in Express.js, configure them for Kubernetes probes, check dependency status, and serve appropriate status codes.
Why It Matters
Express.js is the most popular Node.js framework. Adding proper health checks ensures your Express applications work correctly with modern orchestration platforms and monitoring tools.
Real-World Use
DodaTech's Express.js API gateway serves 5000 requests per second with a /healthz endpoint that checks all downstream service health. If any downstream service fails, the gateway reports degraded.
Express Health Check Learning Path
flowchart LR
A[Deep Health Check] --> B[Express Health Check]
B --> C[Liveness Route]
B --> D[Readiness Route]
B --> E[Dependency Checks]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Basic Express Health Check
The simplest health check in Express is a single route that returns 200.
const express = require("express");
function createBasicHealthApp() {
const app = express();
const startTime = Date.now();
app.get("/healthz", (req, res) => {
res.json({
status: "ok",
uptime: Math.floor((Date.now() - startTime) / 1000),
timestamp: new Date().toISOString()
});
});
app.get("/readyz", (req, res) => {
res.json({ status: "ready" });
});
return app;
}
const app = createBasicHealthApp();
app.listen(3000, () => {
console.log("Health endpoints at /healthz and /readyz");
});
// GET /healthz -> 200 {"status":"ok","uptime":42,"timestamp":"..."}
// GET /readyz -> 200 {"status":"ready"}
Health Check with Middleware
Use middleware to track state and reject requests during startup or shutdown.
const express = require("express");
function healthCheckMiddleware() {
let isReady = false;
let isAlive = true;
function setReady(value) {
isReady = value;
}
function setAlive(value) {
isAlive = value;
}
function middleware(req, res, next) {
if (req.path === "/healthz") {
return res.status(isAlive ? 200 : 503).json({
status: isAlive ? "alive" : "dead"
});
}
if (req.path === "/readyz") {
return res.status(isReady ? 200 : 503).json({
status: isReady ? "ready" : "not ready"
});
}
next();
}
return { middleware, setReady, setAlive };
}
const app = express();
const health = healthCheckMiddleware();
app.use(health.middleware);
app.get("/api/data", (req, res) => res.json({ data: "test" }));
setTimeout(() => health.setReady(true), 5000);
app.listen(3000);
console.log("Health middleware active");
Dependency Checking in Express
Check database and other dependencies in the readiness route.
const express = require("express");
class ExpressDependencyCheck {
constructor() {
this.checks = new Map();
}
register(name, checkFn, critical = true) {
this.checks.set(name, { checkFn, critical });
}
async runChecks() {
const results = [];
let ready = true;
for (const [name, config] of this.checks) {
try {
const result = await config.checkFn();
results.push({ name, healthy: true, ...result });
} catch (err) {
results.push({ name, healthy: false, error: err.message });
if (config.critical) ready = false;
}
}
return { ready, dependencies: results };
}
createRouter() {
const router = express.Router();
router.get("/healthz", (req, res) => {
res.json({ status: "ok", timestamp: new Date().toISOString() });
});
router.get("/readyz", async (req, res) => {
const result = await this.runChecks();
res.status(result.ready ? 200 : 503).json(result);
});
return router;
}
}
const app = express();
const depCheck = new ExpressDependencyCheck();
depCheck.register("database", async () => {
await new Promise(r => setTimeout(r, 10));
return { latencyMs: 10 };
}, true);
depCheck.register("cache", async () => {
await new Promise(r => setTimeout(r, 5));
return { latencyMs: 5 };
}, false);
app.use("/health", depCheck.createRouter());
app.listen(3000);
console.log("Express dependency health check active");
Express with Kubernetes Probe Configuration
Configure Express health routes for Kubernetes readiness, liveness, and startup probes.
const express = require("express");
function createKubernetesAwareApp() {
const app = express();
let startupComplete = false;
let readyForTraffic = false;
// Startup probe - returns 200 when initialization finishes
app.get("/startupz", (req, res) => {
res.status(startupComplete ? 200 : 503).json({
status: startupComplete ? "started" : "starting"
});
});
// Readiness probe - returns 200 when ready for traffic
app.get("/readyz", (req, res) => {
res.status(readyForTraffic ? 200 : 503).json({
status: readyForTraffic ? "ready" : "not ready"
});
});
// Liveness probe - returns 200 while process is healthy
app.get("/healthz", (req, res) => {
res.json({ status: "alive" });
});
// Simulate startup
setTimeout(() => { startupComplete = true; }, 3000);
setTimeout(() => { readyForTraffic = true; }, 8000);
return app;
}
const app = createKubernetesAwareApp();
const server = app.listen(3000);
// Graceful shutdown
process.on("SIGTERM", () => {
console.log("Shutting down...");
server.close(() => process.exit(0));
});
Health Check with Express Router
Organize health routes into a separate router module.
const express = require("express");
class HealthRouter {
constructor(options = {}) {
this.router = express.Router();
this.startTime = Date.now();
this.version = options.version || "1.0.0";
this.serviceName = options.serviceName || "unknown";
this.setupRoutes();
}
setupRoutes() {
this.router.get("/healthz", (req, res) => {
res.json({
status: "ok",
service: this.serviceName,
version: this.version,
uptime: Math.floor((Date.now() - this.startTime) / 1000),
timestamp: new Date().toISOString()
});
});
this.router.get("/readyz", (req, res) => {
const ready = this.checkReadiness();
res.status(ready ? 200 : 503).json({ ready });
});
this.router.get("/startupz", (req, res) => {
const started = this.checkStartup();
res.status(started ? 200 : 503).json({ started });
});
}
checkReadiness() {
return true;
}
checkStartup() {
return true;
}
getRouter() {
return this.router;
}
}
const app = express();
const healthRouter = new HealthRouter({
serviceName: "user-service",
version: "2.1.0"
});
app.use("/", healthRouter.getRouter());
app.listen(3000);
console.log("Express health router active");
Common Mistakes
Not separating health routes from API routes -- Health checks should not go through authentication middleware or Rate Limiting. Mount them before auth middleware.
Returning 200 for liveness when the Process is stuck -- A simple 200 response doesn't verify the event loop is processing. Add a lightweight async operation.
Making health checks dependent on other middleware -- Body Parsing, session management, and other middleware can fail. Health routes should bypass application middleware.
Not handling the shutdown state -- During shutdown, health endpoints should return 503 to prevent Kubernetes from routing traffic.
Using the same response for all probes -- Kubernetes needs different responses for liveness, readiness, and startup. Differentiate them.
Practice Questions
How do you bypass authentication middleware for health check routes? Mount health routes before the authentication middleware, or use app.use() with path-based exclusion.
What Express response should readiness return during startup? 503 Service Unavailable until initialization is complete, then 200.
How do you track Express server start time for uptime reporting? Store Date.now() in a variable at startup and calculate elapsed time in the health handler.
Challenge: Implement an Express health endpoint that measures event loop lag as a liveness indicator.
function eventLoopLagHealthCheck(maxLagMs = 100) {
let lastCheck = Date.now();
setInterval(() => {
const now = Date.now();
const lag = now - lastCheck - 1000;
if (lag > maxLagMs) {
console.warn(`Event loop lag detected: ${lag}ms`);
}
lastCheck = now;
}, 1000);
return (req, res) => {
const lag = Date.now() - lastCheck - 1000;
const healthy = lag < maxLagMs;
res.status(healthy ? 200 : 503).json({
status: healthy ? "healthy" : "lagging",
eventLoopLagMs: Math.max(0, lag)
});
};
}
const app = require("express")();
app.get("/healthz", eventLoopLagHealthCheck(200));
FAQ
Mini Project
Build an Express.js application with comprehensive health check routes (liveness, readiness, startup), dependency checking middleware, Kubernetes probe configuration, and graceful shutdown integration.
const express = require("express");
function buildCompleteHealthApp() {
const app = express();
const startTime = Date.now();
let ready = false;
let started = false;
setTimeout(() => { started = true; }, 3000);
setTimeout(() => { ready = true; }, 8000);
app.get("/healthz", (req, res) => {
res.json({ status: "ok", uptime: Math.floor((Date.now() - startTime) / 1000) });
});
app.get("/readyz", (req, res) => {
res.status(ready ? 200 : 503).json({ ready });
});
app.get("/startupz", (req, res) => {
res.status(started ? 200 : 503).json({ started });
});
return app;
}
module.exports = buildCompleteHealthApp;
What's Next
Now that you understand Express health checks, learn about Django health check implementation. Then explore Go health check endpoints.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro