Skip to content
/practice·health-check

Health Check Endpoint

beginnerhealth-checkreadinesslivenesshttp
/concept
Health Checks
Letting infrastructure know when your service is ready
Read full guide

Problem

Implement health check endpoints that accurately report whether your server and its dependencies are ready to handle traffic. A /healthz endpoint checks that the server is alive, while /readyz verifies that all downstream dependencies (database, cache) are actually reachable.

Background

In production, orchestrators like Kubernetes use health checks to decide whether to route traffic to a pod, restart it, or wait for it to become ready. A naive health check that always returns 200 hides real problems — your server might be "alive" but unable to serve requests because the database is down. Smart health checks catch this and let the system heal automatically.

There are two types: liveness probes (/healthz) confirm the process isn't deadlocked, and readiness probes (/readyz) confirm the server can actually do work.

Requirements

  • GET /health — basic liveness check, always returns 200 (used by the harness for startup detection)
  • GET /healthz — liveness probe: returns 200 if the server process is running
  • GET /readyz — readiness probe: returns 200 only if both PostgreSQL and Redis are reachable
  • When a dependency is down, /readyz returns 503 with details about which dependencies failed
  • The response body must include a checks object showing the status of each dependency
  • Health checks must complete within 2 seconds (use connection timeouts)

Your Server

  • Your server receives PORT (default 3000), DATABASE_URL, and REDIS_URL environment variables
  • PostgreSQL and Redis are provided as sidecars — they are up when your server starts
  • The harness may stop a sidecar during testing to verify your health check detects the failure

API Contract

`GET /health`

Basic health check (used by harness for startup detection).

  • Status: 200
  • Body: { "status": "ok" }

`GET /healthz`

Liveness probe — is the process alive?

  • Status: 200
  • Body: { "status": "ok" }

`GET /readyz`

Readiness probe — can the server handle requests?

When all dependencies are healthy:

  • Status: 200
  • Body:
json
{
  class="text-pass">"status": class="text-pass">"ok",
  class="text-pass">"checks": {
    class="text-pass">"postgres": { class="text-pass">"status": class="text-pass">"ok" },
    class="text-pass">"redis": { class="text-pass">"status": class="text-pass">"ok" }
  }
}

When one or more dependencies are down:

  • Status: 503
  • Body:
json
{
  class="text-pass">"status": class="text-pass">"error",
  class="text-pass">"checks": {
    class="text-pass">"postgres": { class="text-pass">"status": class="text-pass">"ok" },
    class="text-pass">"redis": { class="text-pass">"status": class="text-pass">"error", class="text-pass">"message": class="text-pass">"Connection refused" }
  }
}

Useful APIs

The starter code provides a pool (pg.Pool) and redis (ioredis) client. Here's how to use them:

code
class=class="text-pass">"text-fg-subtle">// PostgreSQL: get a client from the pool and run a test query
const client = await pool.connect();
try {
  await client.query(class="text-pass">'SELECT 1');
  class=class="text-pass">"text-fg-subtle">// database is reachable
} catch (err) {
  class=class="text-pass">"text-fg-subtle">// database is down
} finally {
  client.release(); class=class="text-pass">"text-fg-subtle">// always release the client back to the pool
}

class=class="text-pass">"text-fg-subtle">// Redis: send PING command
await redis.ping()  class=class="text-pass">"text-fg-subtle">// returns class="text-pass">'PONG' if healthy, throws if down

class=class="text-pass">"text-fg-subtle">// Run multiple async checks in parallel (doesnclass="text-pass">'t short-circuit on failure)
const results = await Promise.allSettled([checkPostgres(), checkRedis()])
class=class="text-pass">"text-fg-subtle">// results[i].status === 'fulfilledclass="text-pass">' → results[i].value
class=class="text-pass">"text-fg-subtle">// results[i].status === 'rejectedclass="text-pass">'  → results[i].reason (no .value!)

class=class="text-pass">"text-fg-subtle">// Set a timeout on a promise
const withTimeout = (promise, ms) =>
  Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), ms))])
Scenarios·4 visible + hidden

Your solution will be tested against these scenarios plus a hidden set.

  1. 01GET /healthz returns 200 with {status: 'ok'}
  2. 02GET /readyz returns 200 with status for both postgres and redis when all dependencies are up
  3. 03GET /readyz response includes a 'checks' object with postgres and redis status
  4. 04GET /readyz responds within 2 seconds

Hints

Click each hint to reveal it. Take your time — try before you peek.