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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Racing a promise against a timeout guarantees a health probe can never hang the whole check.
- 2Catching per-dependency errors lets one failure be reported without collapsing the entire response.
- 3Returning 503 on degraded status makes the endpoint machine-readable for load balancers and monitors.
Related explainers
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
java
public class TimedSocketReader { private static final int READ_TIMEOUT_MS = 5_000; private static final int CONNECT_TIMEOUT_MS = 3_000;
Reading a socket with connect and read timeouts
sockets
timeouts
io
Intermediate
8 steps
javascript
import { useEffect, useRef, useState } from 'react'; export function useDelayedFlag(active, delay = 300) { const [visible, setVisible] = useState(false);
Delaying a loading spinner with a React hook
custom-hooks
debouncing
cleanup
Intermediate
8 steps
javascript
const SWIPE_THRESHOLD = 80; const MAX_TRANSLATE = 120; export function attachSwipeToDismiss(element, onDismiss) {
Building a swipe-to-dismiss gesture in JS
touch-events
gesture-detection
dom-manipulation
Intermediate
10 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-health-check-endpoint-in-express-explained-javascript-9bac/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.