rust
54 lines · 9 steps
An async job queue handler in Axum
An Axum handler accepts an export request, spawns the work in the background, and immediately returns 202 with a status URL.
Explained by
highlit
1use axum::{
2 extract::State,
3 http::StatusCode,
4 response::IntoResponse,
5 Json,
6};
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9use uuid::Uuid;
10
11#[derive(Deserialize)]
12pub struct ExportRequest {
13 pub dataset_id: Uuid,
14 pub format: String,
15}
16
17#[derive(Serialize)]
18pub struct ExportAccepted {
19 pub job_id: Uuid,
20 pub status: &'static str,
21}
22
23pub async fn enqueue_export(
24 State(state): State<Arc<AppState>>,
25 Json(req): Json<ExportRequest>,
26) -> impl IntoResponse {
27 let job_id = Uuid::new_v4();
28 state.jobs.mark_pending(job_id).await;
29
30 let state = state.clone();
31 tokio::spawn(async move {
32 if let Err(err) = run_export(&state, job_id, &req).await {
33 tracing::error!(%job_id, %err, "export job failed");
34 state.jobs.mark_failed(job_id, err.to_string()).await;
35 }
36 });
37
38 (
39 StatusCode::ACCEPTED,
40 [("Location", format!("/exports/{job_id}"))],
41 Json(ExportAccepted { job_id, status: "pending" }),
42 )
43}
44
45async fn run_export(
46 state: &AppState,
47 job_id: Uuid,
48 req: &ExportRequest,
49) -> anyhow::Result<()> {
50 let rows = state.repo.load_dataset(req.dataset_id).await?;
51 let url = state.storage.write_report(&rows, &req.format).await?;
52 state.jobs.mark_complete(job_id, url).await;
53 Ok(())
54}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Returning 202 Accepted with a Location header lets clients poll a long-running job instead of blocking on the response.
- 2Spawning work with tokio::spawn detaches it from the request lifecycle, so the handler returns before the job finishes.
- 3Cloning an Arc state before moving it into a spawned task keeps shared resources available to the background work.
Related explainers
rust
use axum::{extract::{Path, State}, http::StatusCode, Json}; use dashmap::DashMap; use serde::Serialize; use std::sync::Arc;
Request coalescing in an Axum handler
caching
concurrency
request-coalescing
Advanced
8 steps
python
from functools import wraps import asyncio from fastapi import APIRouter, FastAPI, Request
Per-route request timeouts in FastAPI
decorators
async
timeouts
Intermediate
6 steps
rust
use std::net::Ipv4Addr; use std::str::FromStr; #[derive(Debug, Clone, Copy)]
Parsing and matching IPv4 CIDR ranges in Rust
bitwise-operations
parsing
error-handling
Intermediate
8 steps
ruby
class Api::MessagesController < ApiController before_action :authenticate_api_key! rate_limit to: 100,
Layered API rate limiting in Rails
rate-limiting
api-authentication
throttling
Intermediate
9 steps
rust
use std::sync::OnceLock; static CRC_TABLE: OnceLock<[u32; 256]> = OnceLock::new();
Table-driven CRC32 with lazy init in Rust
checksums
lazy-initialization
lookup-tables
Intermediate
8 steps
rust
use axum::{ body::Body, extract::Path, http::{header, HeaderMap, HeaderValue, StatusCode},
HTTP range requests for video streaming in Axum
http-range-requests
streaming
async-io
Advanced
8 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/an-async-job-queue-handler-in-axum-explained-rust-bb16/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.