September 9, 2021

RP1: an experimental Diesel-based CRUD for Rocket

RP1: an experimental Diesel-based CRUD for Rocket
Most of our web applications use either Node.js or Symfony for their server-side part. Both offer a lot in terms of productivity. But every now and again, when you look at the computing power used or the amount of time a simple HTTP request takes, you can't help to think "what if..?".

So, a couple of years back, we explored Rust for the web. While we liked the promises, we felt the ecosystem wasn't quite ready. This year we've re-explored Actix and Rocket, got convinced quickly and started using both in production.

Eventually, we decided that Rocket offers more of what we need and was more suited to our style of development. Internally we were discussing how we could help Rocket progress to a stable version 0.5 (with compilation on stable rust and with async support), only to be outdone by the people working on Rocket, who recently released the first release candidate. So instead we focussed our efforts on a common use case for our company: the CRUD.

Rocket provides a very efficient and very fast way to handle HTTP requests. Meanwhile, Diesel is a complete ORM for Rust that allows efficient communication with a database. While both work really well, we wanted something to be able to rapidly prototype a REST-like API. This is why we decided to create RP1.

RP1 is a procedural macro that generates a set of useful basic CRUD (Create-Read-Update-Delete) endpoints in a REST-like API with JSON output. Let's take a look at how RP1 can help you.


When querying items from the database with Diesel you have to implement a struct that implements the diesel::Queryable trait. This struct is the base for RP1 macro. Let's look at an example:

#[rp1::crud(database = "Db", table_name = "users")]
#[derive(Debug, serde::Serialize, diesel::Queryable)]
struct User {
    pub id: i32,
    username: String,
    role: String,
    created_at: chrono::NaiveDateTime,
    updated_at: chrono::NaiveDateTime,

The macro now generates the endpoints (and some additional utilities), which are exposed as a get_routes associated function.

struct Db(diesel::PgConnection);

fn rocket() -> _ {
        .mount("/users", User::get_routes())

This will generate these routes:

  • GET /users to get a list of all users
  • POST /users to create a new user based on a JSON or form encoded body
  • GET /users/1 to get a user with id 1
  • PATCH /users/1 to update a user with some new values (either as JSON or form encoded again)
  • DELETE /users/1 to delete a user

Additionally, the list endpoint has additional support for filtering, sorting, and pagination, all generated automatically. Make sure you take a look at the documentation, specifically take a look at the macro docs.


The biggest challenge is that one size does not fit all. We're still figuring out at what point it makes more sense to just write the endpoints without (this level of) code generation.

An open technical problem is the retrieval of related entities. Currently, getting a user and all their posts takes two separate queries. Related entities open up a big configuration design space: what if there are multiple related entities, what if I only want 10 most recent posts for every user. We hope to experiment with possible designs in the future.

There are more challenges that need overcoming in the future. Please take a look at our project board to see some of the things we currently know are pain points or which would still need improvements. Of course, we would very much welcome any additional suggestions.


We learned a lot working on this project. It was our first proper introduction to writing procedural macros. We also hit some neat things in the type machinery diesel uses to encode the database as rust types and values.

Still, we're not 100% satisfied with our current solution and continue the search for an approach that is convenient to use and fast to compile. Let us know if you have ideas on how to get there.

Stay up-to-date

Stay up-to-date with our work and blog posts?

Related articles

Just because we're engineers, doesn´t mean we build ALL our applications ourselves. But sometimes inspiration hits and good things happen. So our company planner is now canvas-rendered, has a Rust backend and works like a charm.
At RustNL 2023, a Rust conference held in Amsterdam recently, I had the opportunity to talk about ntpd-rs, our project implementing the Network Time Protocol.
For some time, we have been quietly laying the groundwork for a new implementation of sudo in Rust. Now we are excited to talk about it!