Express.js is the most popular Node.js web framework — millions of apps are built on it. But it has no built-in health check mechanism, and it gives you no warnings when it is struggling under load or when a dependency fails.
This guide shows how to add external uptime monitoring to your Express.js application with Vigilmon.
Step 1: Add a Health Check Route
Express makes it simple to add a /health endpoint:
const express = require("express");
const app = express();
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
app.listen(3000);
For a more comprehensive health check that verifies your dependencies:
const { Pool } = require("pg");
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
app.get("/health", async (req, res) => {
const checks = {};
// Check PostgreSQL
try {
await pool.query("SELECT 1");
checks.database = "ok";
} catch (e) {
checks.database = `error: ${e.message}`;
}
// Check Redis if applicable
if (redisClient) {
try {
await redisClient.ping();
checks.cache = "ok";
} catch (e) {
checks.cache = "error";
}
}
const isHealthy = Object.values(checks).every(v => v === "ok");
res.status(isHealthy ? 200 : 503).json({
status: isHealthy ? "ok" : "degraded",
checks,
});
});
Vigilmon treats any non-2xx response as a failure. Return 503 when degraded — this triggers the alert automatically.
Step 2: Protect the Health Endpoint
If your health endpoint exposes sensitive dependency information, protect it:
const MONITORING_TOKEN = process.env.MONITORING_TOKEN;
app.get("/health/detailed", (req, res) => {
const token = req.headers.authorization?.split("Bearer ")[1];
if (token !== MONITORING_TOKEN) {
return res.status(403).json({ error: "Forbidden" });
}
// Return detailed health info
res.json({ status: "ok", database: "ok", memory: process.memoryUsage() });
});
Configure Vigilmon to send Authorization: Bearer your-monitoring-token with each request.
Step 3: Track Process Health
Express does not automatically restart when it runs out of memory or when the event loop is blocked. Add metrics to your health endpoint:
app.get("/health", (req, res) => {
const mem = process.memoryUsage();
const heapUsedMB = Math.round(mem.heapUsed / 1024 / 1024);
const heapTotalMB = Math.round(mem.heapTotal / 1024 / 1024);
const heapPercent = Math.round((mem.heapUsed / mem.heapTotal) * 100);
// Alert if memory is over 90%
const memStatus = heapPercent > 90 ? "warning" : "ok";
res.json({
status: memStatus === "ok" ? "ok" : "degraded",
checks: { memory: memStatus },
metrics: {
heapUsedMB,
heapTotalMB,
heapPercent,
uptime: Math.round(process.uptime()),
},
});
});
Step 4: Set Up Vigilmon
- Sign up at vigilmon.online — free for 50 monitors, 1-minute checks
- Add your Express health endpoint
- Set check regions: pick 2-3 closest to your users
- Set timeout to match your slowest acceptable response time
- Connect Slack or email for alerts
Handling Multiple Express Instances
If you run multiple Express instances behind a load balancer, monitor:
- The load balancer URL — this tests the full stack
- Individual instance health — if your load balancer exposes instance endpoints
A load balancer can route traffic to a broken instance while others are fine. Monitoring only the LB URL misses per-instance failures.
Express.js with PM2
If you use PM2 to run Express in production, combine PM2 cluster management with Vigilmon external monitoring:
// ecosystem.config.js
module.exports = {
apps: [{
name: "my-api",
script: "app.js",
instances: "max",
exec_mode: "cluster",
max_memory_restart: "1G", // PM2 restarts if over 1GB
}]
};
PM2 restarts unhealthy instances. Vigilmon alerts you when the overall service is unreachable from the outside — which can happen even when PM2 shows all instances as online.
TypeScript and Express
For TypeScript Express apps:
import express, { Request, Response } from "express";
import { Pool } from "pg";
const app = express();
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
interface HealthResponse {
status: "ok" | "degraded";
checks: Record<string, string>;
}
app.get("/health", async (req: Request, res: Response<HealthResponse>) => {
const checks: Record<string, string> = {};
try {
await pool.query("SELECT 1");
checks.database = "ok";
} catch (e) {
checks.database = "error";
}
const isHealthy = Object.values(checks).every(v => v === "ok");
res.status(isHealthy ? 200 : 503).json({
status: isHealthy ? "ok" : "degraded",
checks,
});
});
app.listen(3000, () => console.log("Server running on port 3000"));
Common Express Failure Modes
- Unhandled promise rejections — crash the process in newer Node.js versions
- Memory leaks — heap grows until OOM kill
- Event loop blocking — synchronous operations block all requests
- Database connection pool exhaustion — new requests queue indefinitely
- Dependency failures — upstream APIs time out, cascading to your responses
Add process.on("unhandledRejection", ...) to catch unhandled rejections, and monitor externally with Vigilmon to catch anything that slips through.
Add uptime monitoring to your Express app at vigilmon.online — free, no credit card, 5 minutes to set up.
Top comments (0)