Design / Web/Rust API
20 / 67

10/2023Design / Web

Rust API

A proof-of-concept REST API in Rust with the Rocket framework and a MongoDB backend, trading Express familiarity for compile-time memory safety.

╌╌╌╌

A REST API written in Rust with the Rocket framework and a MongoDB backend. Building the same service in TypeScript on Express is routine; this was a proof-of-concept for a larger project, testing whether Rust's guarantees carry into everyday web plumbing.

I reached for Rust here because it offers C-like performance without a garbage collector, and its borrow checker rules out use-after-free and data races at compile time rather than at runtime. For an API expected to stay up under concurrent load, that moves a class of failures from production to the build.

The service is a small events API built on one model and four routes. A single Event struct in events.rs carries a name, description, a BSON time, a location, and a Vec<String> of participants, plus an optional _id that serde skips when it is absent — so a client can PUT an event without inventing an id, and Mongo mints the ObjectId on insert. main.rs mounts exactly four handlers: GET / (a health check), GET /events and GET /events/<id> to read all events or one by id, and PUT /events to insert one. It is create-and-read, not full CRUD.

Rocket owns the request lifecycle. Routes are ordinary async functions annotated with a method and path. Rocket parses the path and the JSON body into typed arguments before the handler runs, so a PUT whose body does not deserialize into an Event is rejected before any logic executes. The Mongo client connects once at startup — Database::init reads MONGODB_URI from the environment and opens a handle to the events collection — and that Database lives in Rocket's managed state, so every handler borrows the same shared handle through a &State<Database> argument instead of opening its own.

Rocket deserializes the JSON body into a typed Event, hands each route the one Database it holds in managed state, and the driver reads and writes BSON in a single events collection.

Serde bridges the three type systems in play. A document crosses three representations, JSON on the wire, BSON in the database, and a Rust struct in the handler, and serde derives the serialization and deserialization between them straight from the struct definition. The friction is front-loaded: getting the types and lifetimes to line up across async handlers is the work, after which the compiler guarantees the wiring is sound. Two small macros round it off — db! builds and unwraps the connection at launch, and event! constructs an Event from its fields.

This service backs the live comment features on my blog and my reference notes — open any article and leave a note to see it at work.

References

  1. Project repository

╌╌ END ╌╌