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
javascript
'use client'; import { useEffect } from 'react'; import * as Sentry from '@sentry/nextjs';
How a Next.js error boundary recovers
error-boundary
error-handling
observability
Intermediate
8 steps
javascript
const MAX_BATCH_SIZE = 20; const FLUSH_INTERVAL = 5000; const ENDPOINT = '/api/analytics';
Batching analytics events in the browser
batching
buffering
sendbeacon
Intermediate
10 steps
javascript
export function diffIds(current, desired) { const currentSet = new Set(current); const desiredSet = new Set(desired);
Diffing sets to sync group membership
set-operations
diffing
transactions
Intermediate
8 steps
javascript
class HistoryStack { constructor(initial = '', limit = 100) { this.past = []; this.present = initial;
Building an undo/redo history stack
undo-redo
state-management
debouncing
Intermediate
7 steps
javascript
import { useState, useCallback, useRef } from 'react'; function LikeButton({ postId, initialLiked, initialCount }) { const [liked, setLiked] = useState(initialLiked);
Optimistic like button with React
optimistic-ui
abortcontroller
error-handling
Advanced
7 steps
javascript
import { useState, useRef, useEffect, useCallback } from 'react'; export function CopyButton({ text, label = 'Copy' }) { const [copied, setCopied] = useState(false);
A copy-to-clipboard button in React
clipboard-api
hooks
cleanup
Intermediate
7 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.