rust 46 lines · 6 steps

A timeout-guarded health check in Axum

An Axum handler probes Postgres with a bounded timeout and reports three distinct states as JSON.

Explained by highlit
1use std::time::Duration;
2 
3use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
4use serde::Serialize;
5use sqlx::PgPool;
6use tokio::time::timeout;
7 
8#[derive(Serialize)]
9pub struct HealthResponse {
10 status: &'static str,
11 database: &'static str,
12}
13 
14pub async fn health_check(State(pool): State<PgPool>) -> impl IntoResponse {
15 let probe = sqlx::query_scalar::<_, i32>("SELECT 1").fetch_one(&pool);
16 
17 match timeout(Duration::from_secs(2), probe).await {
18 Ok(Ok(_)) => (
19 StatusCode::OK,
20 Json(HealthResponse {
21 status: "ok",
22 database: "up",
23 }),
24 ),
25 Ok(Err(err)) => {
26 tracing::error!(error = %err, "database health probe failed");
27 (
28 StatusCode::SERVICE_UNAVAILABLE,
29 Json(HealthResponse {
30 status: "degraded",
31 database: "down",
32 }),
33 )
34 }
35 Err(_) => {
36 tracing::warn!("database health probe timed out");
37 (
38 StatusCode::SERVICE_UNAVAILABLE,
39 Json(HealthResponse {
40 status: "degraded",
41 database: "timeout",
42 }),
43 )
44 }
45 }
46}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Wrapping an async I/O call in a timeout keeps a probe from hanging forever on a stuck dependency.
  2. 2Nested Result matching lets you distinguish a timeout from an error from success and respond differently to each.
  3. 3Returning a status code paired with JSON gives clients both a machine-readable signal and structured detail.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A timeout-guarded health check in Axum — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code