Skip to content

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.

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.

CrateRoleWhy it earns its place
tokioasync runtimeone server, thousands of concurrent requests
axumweb frameworktyped extractors, tower middleware, first-class in the Rust ecosystem
sqlxasync SQLreal Postgres queries, checked at compile time, no ORM magic
serde(de)serializationJSON ⇄ your structs, derived
thiserrorerror typesone AppError that maps cleanly to HTTP
uuid / timeids & timestampsstable 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 └──────────┘
ChTitleYou addThe idea it teaches
1Project shapeworkspace, axum server, config, /healthbinary vs library split, config as types, the app skeleton
2PersistencePostgres, sqlx, migrations, a repositoryconnection pools, migrations as code, keeping SQL out of handlers
3The HTTP APIfull posts CRUD, extractors, DTOsrequest/response types, validation, slugs vs ids
4The error storyAppError, IntoResponse, loggingone 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/.

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.

Next: Part 2 · The Frontend — Astro on Rust →