Skip to content

Build a posts feature end to end

From hello to a running apps/blog binary — HTTP CRUD behind the guards, validation, Postgres, and an e2e test, one runnable page at a time.

You ship one feature — posts — into the Publish story’s blog app: entity, service, a PostsController behind the guards, validation, Postgres, and an e2e test. Each page leaves you with code that compiles and a command you can run.

Pages 1–7 build your blog. Pages 8 and 9 read the reference api. That is a deliberate switch, and worth knowing before you reach it: blog’s Post has an id, a title and a body — no tenant column, no relation — because a small domain is what makes pages 2 to 7 short. Per-caller row filtering and an auto-resolved GraphQL relation both need a domain that has those things, so the last two pages walk through the feature that does (api’s users) rather than first sending you to add a column you have no use for. You still finish each one with something to do in your own blog.

hello for the quick-start binary, apps/blog/ for the tutorial app, a posts/ feature with HTTP only, and the three auth roots nestrs g auth writes on page 4 — a DB-backed route is guarded from its first request.

  • Directoryapps/blog/
    • Cargo.toml
    • Directorysrc/
      • main.rs
      • module.rs
    • Directorytests/
      • Directorye2e/
        • main.rs
  • Directorycrates/
    • Directoryfeatures/
      • Directorysrc/
        • Directoryidentity/
        • Directoryauthn/
        • Directoryauthz/
        • Directoryposts/
          • entity.rs
          • service.rs
          • module.rs
          • mod.rs
          • Directoryhttp/
            • controller.rs
            • module.rs
            • mod.rs

Two curls against the running binary, carrying the development token POST /auth/dev-token mints on Persist through Postgres. The bearer is not optional: Repo filters every read by the caller’s ability, so the guards go on with the database, not later.

Terminal window
$ curl -s -X POST http://localhost:3005/posts -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"title":"Hello","body":"World"}'
{"id":"018f…","title":"Hello","body":"World"}
$ curl -s http://localhost:3005/posts/018f… -H "Authorization: Bearer $TOKEN"
{"id":"018f…","title":"Hello","body":"World"}

And a passing e2e suite:

Terminal window
$ nestrs run test e2e
PASS [ …] blog::e2e posts_round_trip
  • A workspace scaffolded per Getting started — the nestrs CLI installed and nestrs run dev hello serving Hello World from crates/features/src/hello/.
  • Rust 1.97+, and enough of Fundamentals, Configuration, and HTTP that module, provider, and controller are familiar terms.
  • Docker available — the database and testing pages need Postgres (nestrs run db up); earlier pages run without one.
  • Page 1 adds apps/blog/ with nestrs new blog; everything after that happens in blog, not hello.
  • Scaffold the app — the next step: add apps/blog/ with nestrs new blog.
  • Publish — the reference workspace this tutorial builds toward.
  • Getting started — the workspace setup this tutorial assumes.