Rust Backend with Axum
Build a blog API in Rust step by step with Axum 0.8: routing, JSON, state, error handling, modules and configuration.
Part 1
Rust Web Server with Axum 0.8: Your First Server
Build your first Rust web server with Axum 0.8 and Tokio: a handler, a Router, tokio::main, a path extractor, and the errors you will hit along the way (E0752, the ":id" panic, 400 and 404). Copy-paste ready code.
Part 2
Rust Web Server with Axum 0.8: JSON In and Out with Serde
Send and receive JSON in Axum 0.8 with Serde: derive Serialize and Deserialize, the Json extractor, 201 Created, and the 415, 422 and 400 request errors.
Part 3
Rust Web Server with Axum 0.8: In-Memory CRUD with State
Episode 3 of the Rust backend series: keep posts in memory with Arc, Mutex and with_state, fix the missing Clone error, and build all five CRUD routes with 201, 204 and 404.
Part 4
Rust Web Server with Axum 0.8: Error Handling with Result, ? and IntoResponse
Give an Axum API real error messages: an error enum, impl IntoResponse, Result and the ? operator in every handler, and what the E0277 Handler error really means.
Part 5
Rust Web Server with Axum 0.8: JSON Errors with thiserror and From
Give every Axum error the same JSON body with thiserror, catch axum's JsonRejection, and fix E0277 '? couldn't convert the error' with #[from].
Part 6
Rust Web Server with Axum 0.8: Project Structure with Modules
Split one long main.rs into models, error and handlers modules with mod, use, pub and crate::, and fix E0425, E0603 and E0616 on the way.
Part 7
Rust Web Server with Axum 0.8: Configuration with .env, std::env and OnceLock
Read the port and host from environment variables, load a .env file with dotenvy, read the config once with OnceLock, and turn a bad PORT value into a clear panic.
Part 8
Rust Web Server with Axum 0.8: Logging with tracing, RUST_LOG and TraceLayer
Replace println with the tracing crates: log levels and fields, an EnvFilter set by RUST_LOG, TraceLayer from tower-http for every request, a warn! event in our errors, and #[instrument] spans.
Part 9
Rust Web Server with Axum 0.8: A Real Database with SQLx and SQLite
Connect the Axum API to SQLite with SQLx: a SqlitePool in the state, create_if_missing and WAL mode, FromRow and query_as, so posts survive a restart.
Part 10
Rust Web Server with Axum 0.8: SQLx Migrations and Compile-Time Checked Queries
Move the schema into SQLx migrations with sqlx-cli, run them at start with migrate!, and let the query_as! macro check every SQL query at compile time, with sqlx prepare and SQLX_OFFLINE for builds without a database.
Part 11
Rust Web Server with Axum 0.8: Full CRUD with a Repository Trait
Put every database call behind a PostRepo trait with async methods, see why Arc<dyn PostRepo> fails with E0038, and make the handlers generic over the repository instead.
Part 12
Rust Web Server with Axum 0.8: Comments, Foreign Keys and ON DELETE CASCADE
Add a comments table that references posts, nested /posts/{id}/comments routes, ON DELETE CASCADE, and turn a FOREIGN KEY constraint failure into a clean 404.
Part 13
Rust Web Server with Axum 0.8: Transactions, 409 Conflict and "database is locked"
Give posts a unique slug and answer a duplicate with 409 Conflict instead of 500, save a post and its tags all or nothing with an SQLx transaction, and see "database is locked" after SQLite's busy timeout.
Part 14
Rust Web Server with Axum 0.8: Pagination, Filters and Sorting with Query
Read page, per_page, tag and sort from the URL with axum's Query extractor, build the SQL safely with SQLx's QueryBuilder and push_bind, return a Page with a total count, and turn bad query strings into a JSON 400.
Part 15
Rust Web Server with Axum 0.8: Full-Text Search with FTS5, and Soft Delete
Search posts by their words with an SQLite FTS5 index kept in sync by triggers, rank with bm25 and show snippets, quote user input so a stray quote cannot break the query, and move deleted posts to a trash with a deleted_at column.
Part 16
Rust Web Server with Axum 0.8: Validation with the validator Crate and 422
Validate request bodies with the validator crate's derive (length rules, custom functions and messages), build our own ValidJson extractor with FromRequest, and answer one 422 that lists every bad field.
Part 17
Rust Web Server with Axum 0.8: Sign-Up with argon2 Password Hashing
Add a users table and a /signup route, see why a plain-text password is a disaster, hash passwords with argon2 and a random salt, run the slow hash with spawn_blocking, and answer 409 for a taken email.
Part 18
Rust Web Server with Axum 0.8: Login with JSON Web Tokens and 401
Log users in with a JSON Web Token: claims with sub, role and exp, a secret from .env, jsonwebtoken 11's crypto backend feature, argon2 verify_password, one 401 for every login failure, and a /me route that rejects a tampered token.
Part 19
Rust Web Server with Axum 0.8: An Auth Extractor with FromRequestParts
Turn the token check into an AuthUser extractor with FromRequestParts, protect every route that changes data, put the body extractor last to fix E0277, store an author_id on posts, and answer 401 for every missing or broken token.
Part 20
Rust Web Server with Axum 0.8: Refresh Tokens and HttpOnly Cookies
Make access tokens short-lived and add long-lived refresh tokens: random bytes stored as SHA-256 hashes, sent in an HttpOnly, Secure, SameSite cookie with axum-extra's CookieJar, rotated on every use, and deleted on logout.
Part 21
Rust Web Server with Axum 0.8: Roles, Ownership and 403 Forbidden
Decide what a logged-in user may do: 401 versus 403, a Role enum parsed with FromStr, an owner-or-admin rule for editing posts, and an AdminUser extractor for admin-only routes.
Part 22
Rust Web Server with Axum 0.8: Log In with GitHub (OAuth 2)
Add "Log in with GitHub" to the Axum API with the oauth2 crate: the authorization code flow, state and PKCE in a short cookie, the code exchange with reqwest, GitHub's user-agent rule behind a 403, and GitHub users stored in our own users table with our own tokens.
Part 23
Rust Web Server with Axum 0.8: CORS, Security Headers and Rate Limiting with 429
Prepare the API for the real internet: a CorsLayer with an explicit list of origins, three security headers with SetResponseHeaderLayer, and per-IP rate limiting on the login routes with tower_governor, including the "Unable To Extract Key" fix with ConnectInfo.
Part 24
Rust Web Server with Axum 0.8: File Uploads with Multipart, 413 and 415
Upload images to posts with axum's Multipart extractor (behind the multipart feature), save them under our own UUID names, serve them with ServeDir, raise the 2 MB default with DefaultBodyLimit, and check magic bytes to answer 415 for fake images.
Part 25
Rust Web Server with Axum 0.8: A Storage Trait for Local Disk and Memory
Hide where uploaded files live behind a Storage trait with put, get and delete, implement it for the local disk (with a safe file-name check) and for memory, pass it to handlers as an Extension, and pick the store from an environment variable.
Part 26
Rust Web Server with Axum 0.8: Sending Email with lettre
Send a welcome email on sign-up with lettre's async SMTP transport and a local test mail server, see a down mail server turn sign-up into a 500, and make the email non-fatal with a logged warning.
Part 27
Rust Web Server with Axum 0.8: Background Jobs and Timers with tokio::spawn and mpsc
Stop making users wait for email: a Job enum, a bounded Tokio mpsc channel as a job queue, a worker task started with tokio::spawn, the E0382 "use of moved value" on the way, and an hourly cleanup timer with tokio::time::interval.
Part 28
Rust Web Server with Axum 0.8: Caching with RwLock, then Redis
Cache posts in memory with an RwLock, hit the hidden "future is not Send" error from a lock guard held across .await, invalidate on every write, then move the cache to Redis with JSON values, a 60-second expiry, and a fallback when Redis is down.
Part 29
Rust Web Server with Axum 0.8: Live Comments over WebSocket with tokio::select!
Push new comments to every reader in real time: axum's ws feature and WebSocketUpgrade, a Tokio broadcast channel, tokio::select! to wait for the channel and the client at once, a per-post filter, Lagged receivers, and a clean close.
Part 30
Rust Web Server with Axum 0.8: Server-Sent Events
Stream live comments one way over plain HTTP with server-sent events: the SSE text format, axum's Sse with a Stream, tokio-stream's BroadcastStream and filter_map, KeepAlive for quiet connections, curl -N, and EventSource in the browser.
Part 31
Rust Web Server with Axum 0.8: API Versioning with nest, merge and Deprecation Headers
Rename a JSON field without breaking old clients: put routes under /api/v1 and /api/v2 with Router::nest, keep the old root paths with merge (nesting at the root panics in axum 0.8), add a PostV2 with From, and send Deprecation, Sunset and Link headers on v1.
Part 32
Rust Web Server with Axum 0.8: Timeouts (408) and Graceful Shutdown
Cancel requests that hang with tower-http's TimeoutLayer and a 408, keep WebSocket and SSE routes outside the timeout, and shut down gracefully on Ctrl+C and SIGTERM so running requests finish before the server exits.
Part 33
Rust Web Server with Axum 0.8: Health Checks and Prometheus Metrics
Add /health/live and /health/ready (503 when the database or Redis is down), count and time every request with a middleware and the metrics crate, expose Prometheus text at /metrics, and keep label cardinality small with MatchedPath in a route_layer.
Part 34
Rust Web Server with Axum 0.8: OpenAPI Docs and Swagger UI with utoipa
Generate an OpenAPI file from the Axum code with utoipa: #[utoipa::path] on handlers, ToSchema on types (with doc comments and examples), an OpenApi struct, Swagger UI served from the app, and a bearer security scheme for protected routes.
Part 35
Rust Web Server with Axum 0.8: Testing with Unit Tests and Router Tests
Write unit tests with #[cfg(test)] and find a real slugify bug, then test the whole Axum router without a network using tower's oneshot, a fresh SQLite database per test with #[sqlx::test], a login helper, and the ConnectInfo a rate limiter needs.
Part 36
Rust Web Server with Axum 0.8: Docker, One Image That Runs Anywhere
Package the Axum server as a small Docker image: a two-stage Dockerfile with a cached dependency layer, SQLX_OFFLINE builds, a .dockerignore that keeps secrets out, settings from environment variables, Redis on a Docker network, a data volume, and a non-root user.
Part 37
Rust Web Server with Axum 0.8: CI with GitHub Actions, and Deploy
Run cargo fmt --check, clippy with -D warnings and cargo test on every push with GitHub Actions, fix the three real failures on the way, build and push the Docker image to GitHub's container registry, and deploy by pulling and running it.
Part 38
Rust Web Server with Axum 0.8: Declarative Macros with macro_rules!
Write declarative macros with macro_rules!: patterns and fragments (expr, ident, ty), why square!(2 + 1) gives 9, repetition with $(...),*, an optional trailing comma, reading the expanded code, hygiene, and a real macro that shrinks a 19-method fake repository.
Part 39
Rust Web Server with Axum 0.8: Writing a Procedural Macro, Our Own Derive
Write a derive macro, #[derive(Columns)], in its own proc-macro crate: TokenStream in and out, syn to parse the struct, quote to generate code, a proper compile error with a span instead of a panic, and a #[columns(prefix = "p")] helper attribute.
Part 40
Rust Web Server with Axum 0.8: How Future and Pin Really Work
Open the async box: implement Future by hand with poll, Ready and Pending, see a program hang when the Waker is forgotten, learn why async fn state machines need Pin, hit the Unpin error, and write a Timed wrapper future with pin-project-lite that logs slow database queries.
Part 41
Rust Web Server with Axum 0.8: unsafe Rust and Calling C (FFI)
Learn what unsafe allows by calling C from the Axum server: sqlite3_libversion through libsqlite3-sys and CStr, E0133, SAFETY comments, compiling our own C file with build.rs and the cc crate, unsafe extern "C", a dangling CString pointer, NulError, and a safe wrapper.
Part 42
Rust Web Server with Axum 0.8: Profiling and Performance
Measure before you optimize: load-test the Axum API with oha, compare debug and release builds, find the hot spot with perf, read SQLite's EXPLAIN QUERY PLAN, add an index for sorting, and measure again; plus why a slow, rate-limited login is a feature.