rust 57 lines · 8 steps

Paginated, filtered product listing in Axum

An Axum handler parses query params into a typed struct and runs one SQL query where each filter is optional.

Explained by highlit
1use axum::{
2 extract::{Query, State},
3 http::StatusCode,
4 Json,
5};
6use serde::Deserialize;
7use sqlx::PgPool;
8 
9fn default_page() -> u32 {
10 1
11}
12 
13fn default_per_page() -> u32 {
14 20
15}
16 
17#[derive(Debug, Deserialize)]
18pub struct ListProductsParams {
19 #[serde(default)]
20 pub search: Option<String>,
21 pub category_id: Option<i64>,
22 pub in_stock: Option<bool>,
23 #[serde(default = "default_page")]
24 pub page: u32,
25 #[serde(default = "default_per_page")]
26 pub per_page: u32,
27}
28 
29pub async fn list_products(
30 State(pool): State<PgPool>,
31 Query(params): Query<ListProductsParams>,
32) -> Result<Json<Vec<Product>>, StatusCode> {
33 let per_page = params.per_page.clamp(1, 100) as i64;
34 let offset = (params.page.saturating_sub(1) as i64) * per_page;
35 
36 let products = sqlx::query_as::<_, Product>(
37 r#"
38 SELECT id, name, price, category_id, stock
39 FROM products
40 WHERE ($1::text IS NULL OR name ILIKE '%' || $1 || '%')
41 AND ($2::bigint IS NULL OR category_id = $2)
42 AND ($3::bool IS NULL OR (stock > 0) = $3)
43 ORDER BY name
44 LIMIT $4 OFFSET $5
45 "#,
46 )
47 .bind(params.search)
48 .bind(params.category_id)
49 .bind(params.in_stock)
50 .bind(per_page)
51 .bind(offset)
52 .fetch_all(&pool)
53 .await
54 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
55 
56 Ok(Json(products))
57}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Serde defaults let optional query params fall back to sensible values without manual checks.
  2. 2A single SQL statement with NULL guards handles every combination of optional filters cleanly.
  3. 3Clamping and saturating arithmetic turn untrusted pagination input into safe bounds.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Paginated, filtered product listing in Axum — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code