Skip to content

Health

Health probes answer the questions an orchestrator asks: should you keep this pod running? should you route traffic to it? has it finished booting? The probes ship as routes on the HTTP transport. Importing HealthModule mounts three — GET /health/live, GET /health/ready, GET /health/startup — each returning 200 and a JSON body when every indicator passes, 503 with the same shape and error populated when any indicator fails. Routing rides on poem (the same engine HttpModule mounts), and discovery uses inventory gated by the access graph.

Terminal window
cargo add nest-rs-health
apps/api/src/module.rs
use nest_rs_core::module;
use nest_rs_health::HealthModule;
#[module(imports = [HealthModule])]
pub struct ApiModule;

Three routes appear at boot:

Terminal window
$ curl -s http://localhost:8080/health/live
{"status":"up","info":{},"error":{},"details":{}}
$ curl -s http://localhost:8080/health/ready
{"status":"up","info":{},"error":{},"details":{}}
$ curl -s http://localhost:8080/health/startup
{"status":"up","info":{},"error":{},"details":{}}

With no indicators registered every probe reports up with empty buckets — useful for the first ten minutes; not for production. See Indicators for the framework-shipped ones and how to write your own.

ProbeQuestion the orchestrator asksDefault with no indicators
liveIs the process still alive — should I restart it?up
readyShould I send traffic to it right now?up
startupHas it finished its long boot?up

The split mirrors the orchestrator’s: a long-booting app fails startup until ready (so Kubernetes does not kill it for slow liveness during boot); a temporarily overloaded app fails ready (so traffic drains) without flagging live (the process is fine — do not restart it).

Every probe returns the same body. info holds the up indicators, error holds the down ones, details carries the union so an operator can grep one bucket without iterating a list.

Terminal window
$ curl -s http://localhost:8080/health/ready | jq
{
"status": "up",
"info": {
"db": { "name": "db", "status": "up" },
"upstream": { "name": "upstream", "status": "up" }
},
"error": {},
"details": {
"db": { "name": "db", "status": "up" },
"upstream": { "name": "upstream", "status": "up" }
}
}

When one indicator drops, the response moves to 503 and the indicator migrates from info to error with its stringified cause:

Terminal window
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/health/ready
503
$ curl -s http://localhost:8080/health/ready | jq
{
"status": "down",
"info": { "db": { "name": "db", "status": "up" } },
"error": {
"upstream": {
"name": "upstream",
"status": "down",
"error": "connection refused"
}
},
"details": { "db": { /* ... */ }, "upstream": { /* ... */ } }
}

The overall status is down if any indicator is down. The HTTP status maps from that one bit: 200 for up, 503 for down.

  • Indicators — where they live, the framework-shipped ones, writing a custom one with #[indicators].
  • Discovery — how module-gating decides which indicators run in a given binary.
  • Database / HealthDatabaseHealthModule, the first framework-shipped indicator.
  • HTTP — the transport HealthModule mounts on; same #[controller] shape under the hood.