Skip to content

Express.js Health Check — Complete Implementation Guide

DodaTech Updated 2026-06-28 7 min read

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

  1. Not separating health routes from API routes -- Health checks should not go through authentication middleware or Rate Limiting. Mount them before auth middleware.

  2. 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.

  3. Making health checks dependent on other middleware -- Body Parsing, session management, and other middleware can fail. Health routes should bypass application middleware.

  4. Not handling the shutdown state -- During shutdown, health endpoints should return 503 to prevent Kubernetes from routing traffic.

  5. Using the same response for all probes -- Kubernetes needs different responses for liveness, readiness, and startup. Differentiate them.

Practice Questions

  1. 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.

  2. What Express response should readiness return during startup? 503 Service Unavailable until initialization is complete, then 200.

  3. 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.

  4. 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

Should health check routes be in a separate file?

Yes. Keep health routes in a separate module (health.js or healthRoutes.js) for clarity and reusability across services.

Can I use Express middleware to log health check requests?

Logging health checks fills logs with noise. Use a separate middleware for health routes that doesn't log.

How do I handle CORS for health check endpoints?

Health checks are usually internal. If they need CORS, add the cors middleware only to health routes, not to all routes.

Should health check responses include stack traces on failure?

Never. Health check responses are parsed by automated systems. Include structured error info, never stack traces.

What Express version should I use for health checks?

Express 4.x and 5.x both work. The health check patterns are the same regardless of version.

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