javascript 61 lines · 9 steps

Building a health check endpoint in Express

A /health route probes every dependency in parallel, each with its own timeout, and reports overall status.

Explained by highlit
1const express = require('express');
2const router = express.Router();
3const { pool } = require('../db');
4const redis = require('../redis');
5const axios = require('axios');
6 
7const withTimeout = (promise, ms) =>
8 Promise.race([
9 promise,
10 new Promise((_, reject) =>
11 setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms)
12 ),
13 ]);
14 
15async function checkPostgres() {
16 const start = Date.now();
17 await withTimeout(pool.query('SELECT 1'), 2000);
18 return { status: 'up', latencyMs: Date.now() - start };
19}
20 
21async function checkRedis() {
22 const start = Date.now();
23 const pong = await withTimeout(redis.ping(), 1000);
24 if (pong !== 'PONG') throw new Error(`unexpected reply: ${pong}`);
25 return { status: 'up', latencyMs: Date.now() - start };
26}
27 
28async function checkPaymentsApi() {
29 const start = Date.now();
30 await withTimeout(
31 axios.get(`${process.env.PAYMENTS_URL}/status`, { timeout: 3000 }),
32 3000
33 );
34 return { status: 'up', latencyMs: Date.now() - start };
35}
36 
37router.get('/health', async (req, res) => {
38 const checks = { postgres: checkPostgres, redis: checkRedis, payments: checkPaymentsApi };
39 
40 const results = await Promise.all(
41 Object.entries(checks).map(async ([name, check]) => {
42 try {
43 return [name, await check()];
44 } catch (err) {
45 return [name, { status: 'down', error: err.message }];
46 }
47 })
48 );
49 
50 const dependencies = Object.fromEntries(results);
51 const healthy = Object.values(dependencies).every((d) => d.status === 'up');
52 
53 res.status(healthy ? 200 : 503).json({
54 status: healthy ? 'ok' : 'degraded',
55 uptime: process.uptime(),
56 timestamp: new Date().toISOString(),
57 dependencies,
58 });
59});
60 
61module.exports = router;
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Racing a promise against a timeout guarantees a health probe can never hang the whole check.
  2. 2Catching per-dependency errors lets one failure be reported without collapsing the entire response.
  3. 3Returning 503 on degraded status makes the endpoint machine-readable for load balancers and monitors.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a health check endpoint in Express — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code