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.
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
╌╌ END ╌╌