rust
57 lines · 8 steps
Building a JSON user API in Axum
A small Axum router wires two async handlers to Postgres, mapping database rows to typed JSON responses.
Explained by
highlit
1use axum::{
2 extract::{Path, State},
3 http::StatusCode,
4 routing::get,
5 Json, Router,
6};
7use serde::Serialize;
8use sqlx::PgPool;
9
10#[derive(Clone)]
11struct AppState {
12 pool: PgPool,
13}
14
15#[derive(Serialize)]
16struct User {
17 id: i64,
18 email: String,
19 display_name: String,
20}
21
22pub fn routes(pool: PgPool) -> Router {
23 let state = AppState { pool };
24 Router::new()
25 .route("/users", get(list_users))
26 .route("/users/:id", get(get_user))
27 .with_state(state)
28}
29
30async fn list_users(State(state): State<AppState>) -> Result<Json<Vec<User>>, StatusCode> {
31 let users = sqlx::query_as!(
32 User,
33 "SELECT id, email, display_name FROM users ORDER BY id LIMIT 100"
34 )
35 .fetch_all(&state.pool)
36 .await
37 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
38
39 Ok(Json(users))
40}
41
42async fn get_user(
43 State(state): State<AppState>,
44 Path(id): Path<i64>,
45) -> Result<Json<User>, StatusCode> {
46 let user = sqlx::query_as!(
47 User,
48 "SELECT id, email, display_name FROM users WHERE id = $1",
49 id
50 )
51 .fetch_optional(&state.pool)
52 .await
53 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
54 .ok_or(StatusCode::NOT_FOUND)?;
55
56 Ok(Json(user))
57}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Axum's State extractor threads a shared, cloneable resource like a connection pool through every handler without globals.
- 2Returning Result<Json<T>, StatusCode> lets handlers convert database errors and missing rows into clean HTTP responses.
- 3sqlx's query_as! maps SQL columns directly into a typed struct, giving you compile-time-checked, serializable results.
Related explainers
rust
use axum::{ http::StatusCode, response::IntoResponse, routing::get,
Serving an SPA and API with Axum
routing
static-files
spa-fallback
Intermediate
7 steps
go
func RecoveryHandler(logger *zap.Logger) gin.HandlerFunc { return gin.CustomRecovery(func(c *gin.Context, recovered any) { var brokenPipe bool if ne, ok := recovered.(*net.OpError); ok {
A panic-recovery middleware in Gin
middleware
panic-recovery
structured-logging
Intermediate
6 steps
javascript
const STORAGE_KEY = "app_state"; const CURRENT_VERSION = 3; const migrations = {
Versioned state migrations in localStorage
migrations
persistence
versioning
Intermediate
9 steps
rust
use std::time::{Duration, Instant}; pub struct TokenBucket { capacity: f64,
A token bucket rate limiter in Rust
rate-limiting
token-bucket
lazy-refill
Intermediate
8 steps
php
<?php namespace App\Http\Controllers;
Verifying Stripe webhooks in Laravel
webhooks
signature-verification
event-dispatch
Intermediate
8 steps
javascript
const asyncHandler = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); };
Async error handling in Express routes
async-await
error-handling
middleware
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-json-user-api-in-axum-explained-rust-aebc/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.