Express health check endpoint

Add a route that answers 200 when the process is up and a second one that runs select 1 against your database and answers 503 when the query fails, then point a monitor at the second one.

Two different questions get answered by the same endpoint in most Express apps, which is why so many health checks are useless. The first question is whether the process is alive: did Node crash, did the event loop wedge, is anything listening on the port. The second is whether the app can actually serve a request, which depends on a database, and sometimes a cache or a queue as well.

An app answers the first question and fails the second more often than the other way round. The pool is exhausted, the credentials rotated, the database failed over and DNS has not caught up - Express keeps listening the whole time and cheerfully returns 200 to a monitor that then reports a perfect month. Split the two. Give the liveness route to your orchestrator and the readiness route to your uptime monitor, and make readiness return 503 the moment the query throws, because 503 is what Logdash reads as down.

Both routes

routes/health.js
import { Router } from 'express';
import { pool } from '../db.js';

export const health = Router();

// Liveness: the process answered. Nothing else is claimed.
health.get('/healthz', (req, res) => res.status(200).send('ok'));

// Readiness: the process can serve a real request.
health.get('/readyz', async (req, res) => {
  try {
    await pool.query('select 1');
    res.status(200).json({ ok: true });
  } catch {
    res.status(503).json({ ok: false, dependency: 'postgres' });
  }
});

Mount that router before anything that can reject a request. Auth middleware, rate limiting and a strict CORS policy will all happily block a monitor, and the resulting alert is indistinguishable from a real outage until you have wasted twenty minutes on it. If the app depends on more than one thing, run the checks with Promise.all and fail on the first rejection, because a readiness route that reports partial health is a route nobody knows how to act on.

Rules that keep it honest

  • Mount the routes before any auth middleware, or the monitor gets a 401 and you spend an evening debugging an outage that never happened.
  • Query nothing real. select 1 proves the pool can hand out a connection, which is the only thing you need to know.
  • Do not chain a call to every downstream service. Each one you add is a service whose bad night becomes your pager.
  • Budget under a second. The Logdash pinger times out at 10 seconds and files that as down, and a health route sharing an exhausted pool is the slowest route you have.
  • Return a boolean, not a report. Stack traces and env values in a public JSON body are a gift to anyone scanning your domain.

Watch it from outside

  1. 1
    Create the monitor Add a project in Logdash and paste https://api.yourapp.com/readyz. The check runs immediately and stores the status code and response time from that first request.
  2. 2
    Set how often it runs Free plans check every 5 minutes and cover five projects, Builder drops to every minute and Pro to every 15 seconds. Pick the gap you are willing to be down for without knowing.
  3. 3
    Prove the alert path works Stop your database container while the API keeps running. Within one interval the monitor goes down and Telegram delivers the alert with the failing URL and the 503 in it.

Response time is data, not an alarm

Logdash records how long every check took and charts it, and that chart is genuinely useful for spotting the slow drift that precedes a real failure. It does not alert on it. Nothing fires until a check returns a status outside 200 to 399 or fails outright, so if you want slowness to page you, make the endpoint itself decide: time the query, and return 503 when it crosses the threshold you actually care about. That is a better design anyway, because the app knows what slow means for its own dependencies and a monitor sitting on the other side of the internet does not. The same uptime history feeds a public status page, so the readiness route ends up being the single fact your customers, your orchestrator and your phone all read from.

What is the standard health check endpoint for Express?

Express has no built-in route, so it is whatever you write. /health is the common name, and /healthz plus /readyz is the split you want once Kubernetes or a load balancer is involved, because those two callers are asking different questions.

Should the Express health check query the database?

The readiness one should, with a single select 1. The liveness one should not, because a database outage would then restart every container you have and turn a recoverable problem into a cold start under load.

What should an Express health check return when it fails?

503 with a tiny JSON body. Logdash marks the monitor down on anything outside 200 to 399 and alerts on the transition, so a 500 works too, but 503 is the accurate one: the service exists and is temporarily unable to handle the request.

Does the health endpoint need authentication?

No, and adding it usually backfires. The route returns a boolean and nothing else, so there is nothing to protect, while an expired monitor credential would look exactly like a real outage.

Point it at your own URL and watch it for real.

Any public URL · checked every 15 s