Part 1 · The Blog Core — quill
We start where every real project starts: an empty directory and a decision about shape. By the end
of Part 1, quill is a working blog backend — you can create, read, update, list, and delete posts
over HTTP, backed by a real Postgres database, with errors that turn into correct HTTP status codes
instead of panics. It’s small, but it’s built like the real thing, because everything in Parts 2–7
bolts onto this core.
What you’ll build
Section titled “What you’ll build”A REST API for a posts resource, backed by Postgres:
GET /health → liveness probe GET /api/posts → list posts (published only, paginated) POST /api/posts → create a draft (201 Created) GET /api/posts/{slug}→ fetch one post (404 if missing) PUT /api/posts/{id} → update title/body/status (404 if missing) DELETE /api/posts/{id} → delete a post (204, or 404)Every response is JSON. Every failure is a correct status code. The code is organized as a Cargo workspace from day one — not because a blog needs it, but because Parts 3–4 will add a worker binary and shared crates, and it’s far easier to start with the shape than to retrofit it.
The stack, and why each piece is here
Section titled “The stack, and why each piece is here”| Crate | Role | Why it earns its place |
|---|---|---|
| tokio | async runtime | one server, thousands of concurrent requests |
| axum | web framework | typed extractors, tower middleware, first-class in the Rust ecosystem |
| sqlx | async SQL | real Postgres queries, checked at compile time, no ORM magic |
| serde | (de)serialization | JSON ⇄ your structs, derived |
| thiserror | error types | one AppError that maps cleanly to HTTP |
| uuid / time | ids & timestamps | stable public ids, real created_at/updated_at |
┌────────┐ HTTP ┌───────────────────────────────┐ SQL ┌──────────┐ │ client │ ───────► │ axum router → handler │ ──────► │ Postgres │ │ (curl) │ JSON │ extractors, AppState, ? │ sqlx │ posts │ │ │ ◄─────── │ AppError → IntoResponse │ ◄────── │ table │ └────────┘ JSON └───────────────────────────────┘ rows └──────────┘The chapters
Section titled “The chapters”| Ch | Title | You add | The idea it teaches |
|---|---|---|---|
| 1 | Project shape | workspace, axum server, config, /health | binary vs library split, config as types, the app skeleton |
| 2 | Persistence | Postgres, sqlx, migrations, a repository | connection pools, migrations as code, keeping SQL out of handlers |
| 3 | The HTTP API | full posts CRUD, extractors, DTOs | request/response types, validation, slugs vs ids |
| 4 | The error story | AppError, IntoResponse, logging | one error type, ? → HTTP, never leaking internals |
Each chapter teaches the concept the moment the build needs it, then has you build that slice, and ends
with Check your understanding (with answers). The companion crate lives at rust/quill/.
The thread
Section titled “The thread”Once the code compiles, what does it take to make it run for real users? Part 1’s answer is the least
glamorous and most important: a clean shape and honest failures. A handler that can only return
Ok or a typed error — never a panic, never a leaked stack trace — is the difference between a
service you can operate and one that pages you at 3 a.m. Everything fancy in later parts assumes this
foundation is solid.