Building An API in Rust With Rocket - Rs and Diesel - Rs (Clean Architecture) - by Brook Jeynes - Medium
Building An API in Rust With Rocket - Rs and Diesel - Rs (Clean Architecture) - by Brook Jeynes - Medium
Get unlimited access to all of Medium for less than $1/week. Become a member
In this guide I’m going to walk you through the process of building a simple CRUD
API from scratch in Rust using Rocket.rs. I will show you how to create migrations
and access a PostgreSQL database using Diesel.rs and connect everything up to a
React + Typescript front-end. When building the project we will follow Clean
Architecture, although I won’t go into talking too much about what that is as it’s not
the focus of this guide.
You have the latest version of Rust (this guide uses v1.65.0)
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 1/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
The first step is to setup the architecture of the application. Start by creating a
overarching Rust project:
After this, delete the src folder as we won’t be needing it. The next thing we’re going
to do is generate a new project for each layer in the Clean Architecture model. Our
architecture will follow as such that the:
API Layer will handle the API requests and act as our route handler.
Application layer will handle the logic behind the API requests.
Shared layer will hold any other models our project will need such as response
structures.
By the end of this, our project should be looking something like this:
.
├── Cargo.lock
├── Cargo.toml
├── api
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── application
│ ├── Cargo.toml
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 2/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
│ └── src
│ └── lib.rs
├── domain
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── infrastructure
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── shared
├── Cargo.toml
└── src
└── lib.rs
We’re now going to link all of these projects in the top-level Cargo.toml file. Delete
everything inside the file and enter the following:
[workspace]
members = [
"api",
"domain",
"infrastructure",
"application",
"shared",
]
Nice! That’s the majority of our templating finished, now we can get into some
actual fun.
Migrations
Since we’re using Diesel.rs as the database manager, we will need to install the CLI
tool. Diesel CLI has a few dependencies that need to be installed beforehand
depending on what database you’re planning on using:
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 3/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
For this project, we will be using PostgreSQL. This means we only need libpq as a
dependency. Please refer to the docs for each dependency required to find out how to
install it on your operating system.
With libpq installed, we can now run the following command to install Diesel CLI:
With that installed, let’s set up a connection string to our database. In the top-level
project directory, run the following command with your connections details:
Now, we can use the Diesel CLI to do the heavy lifting for us. Navigate into the
infrastructure folder and run the following command:
diesel setup
Using the Diesel CLI tool, we can create a new migration to handle the initial setup
of our posts table.
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 4/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
Diesel CLI will generate a new migration consisting of a name similar to 2022–11–18–
090125_create_posts . The first part is the date the migration was generated with a
unique code followed by the migrations name. Inside this migration folder will be
two files: up.sql , telling Diesel CLI what to apply in the migration, and down.sql ,
Now, let’s go ahead and write some SQL for the migrations.
-- up.sql
-- down.sql
Using the Diesel CLI, we can apply the new migration we just created.
For more information on running migrations with Diesel.rs, visit the official getting
started guide here.
Creating a connection
With our first set of migrations finished and our project architecture laid out, let’s
finally write some Rust code to connect our application to the database.
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 5/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
# infrastructure/Cargo.toml
[package]
name = "infrastructure"
version = "0.1.0"
edition = "2021"
[dependencies]
diesel = { version = "2.0.0", features = ["postgres"] }
dotenvy = "0.15"
// infrastructure/src/lib.rs
use diesel::pg::PgConnection;
use diesel::prelude::*;
use dotenvy::dotenv;
use std::env;
With our connection made, we need to create some models for our database,
namely Post and NewPost .
// domain/src/lib.rs
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 6/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
We’ll use models to define the structs our database and code will use, while schema
# domain/Cargo.toml
[package]
name = "domain"
version = "0.1.0"
edition = "2021"
[dependencies]
rocket = { version = "0.5.0-rc.2", features = ["json"] }
diesel = { version = "2.0.0", features = ["postgres"] }
serde = { version = "1.0.147", features = ["derive"] }
// domain/src/models.rs
use crate::schema::posts;
use diesel::prelude::*;
use rocket::serde::{Deserialize, Serialize};
use std::cmp::{Ord, Eq, PartialOrd, PartialEq};
// Queryable will generate the code needed to load the struct from an SQL state
#[derive(Queryable, Serialize, Ord, Eq, PartialEq, PartialOrd)]
pub struct Post {
pub id: i32,
pub title: String,
pub body: String,
pub genre: String,
pub published: bool,
}
#[derive(Insertable, Deserialize)]
#[serde(crate = "rocket::serde")]
#[diesel(table_name = posts)]
pub struct NewPost {
pub title: String,
pub body: String,
pub genre: String,
}
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 7/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
// domain/src/schema.rs
diesel::table! {
posts (id) {
id -> Int4,
title -> Varchar,
body -> Text,
genre -> Varchar,
published -> Bool,
}
}
The code in schema.rs may vary slightly for you, but the concept still remains. This
file will be updated whenever we run or revert a migration. It’s important to note
that the order of fields in our Post struct and the posts table must match.
As well as defining database models, let’s create a model to structure how our API
responses are going to be formatted. Navigate to shared/src and create a new file
response_models.rs .
# shared/Cargo.toml
[package]
name = "shared"
version = "0.1.0"
edition = "2021"
[dependencies]
domain = { path = "../domain" }
// shared/src/lib.rs
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 8/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
// shared/src/response_models.rs
use domain::models::Post;
use rocket::serde::Serialize;
#[derive(Serialize)]
pub enum ResponseBody {
Message(String),
Post(Post),
Posts(Vec<Post>)
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct Response {
pub body: ResponseBody,
}
The ResponseBody enum will be used to define what types of data can be returned
from our API and the Response struct will define how the response will be
structured.
Setting up Rocket.rs
Wow! That was a lot of setup just for our database, just so we’re all up-to-date, here’s
what the project structure should look like currently:
.
├── Cargo.lock
├── Cargo.toml
├── api
│ └── ...
├── application
│ └── ...
├── domain
│ ├── Cargo.toml
│ └── src
│ ├── lib.rs
│ └── models.rs
├── infrastructure
│ ├── Cargo.toml
│ ├── migrations
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 9/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
│ │ └── 2022–11–18–090125_create_posts
│ │ ├── up.sql
│ │ └── down.sql
│ └── src
│ ├── lib.rs
│ └── schema.rs
└── shared
├── Cargo.toml
└── src
├── lib.rs
└── response_models.rs
With the bulk of our database setup done, let’s begin the setup of the API portion for
the project.
# api/Cargo.toml
[package]
name = "api"
version = "0.1.0"
edition = "2021"
[dependencies]
domain = { path = "../domain" }
application = { path = "../application" }
shared = { path = "../shared" }
With our dependencies and references to other folders set, let’s create a bin folder
to hold main.rs .
.
└── api
├── Cargo.toml
└── src
├── bin
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 10/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
│ └── main.rs
└── lib.rs
main.rs is going to be the entry point of our API, this is where we will define the
routes we plan to use. We’ll start by defining a single route at a time as we build the
application up.
// api/src/lib.rs
// api/src/bin/main.rs
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/api", routes![
post_handler::list_posts_handler,
post_handler::list_post_handler,
])
}
Create a new file called post_handler.rs in src and write the following template
code:
// api/src/post_handler.rs
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 11/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
use rocket::response::status::{NotFound};
use rocket::serde::json::Json;
#[get("/")]
pub fn list_posts_handler() -> String {
todo!()
}
#[get("/post/<post_id>")]
pub fn list_post_handler(post_id: i32) -> Result<String, NotFound<String>> {
todo!()
}
# application/Cargo.toml
[package]
name = "application"
version = "0.1.0"
edition = "2021"
[dependencies]
domain = { path = "../domain" }
infrastructure = { path = "../infrastructure" }
shared = { path = "../shared" }
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 12/29
8/4/23, 11:17 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
// application/src/lib.rs
// application/src/post/mod.rs
// application/src/post/read.rs
use domain::models::Post;
use shared::response_models::{Response, ResponseBody};
use infrastructure::establish_connection;
use diesel::prelude::*;
use rocket::response::status::NotFound;
}
}
It’s important to note that when using Rocket.rs, the panic!() macro will return a
500 InternalServerError and not crash your program.
With the logic for our route written, let’s return back to our post handler to finish off
our two GET routes.
// api/src/post_handler.rs
// ...
#[get("/")]
pub fn list_posts_handler() -> String {
// 👇
New function body!
let posts: Vec<Post> = read::list_posts();
let response = Response { body: ResponseBody::Posts(posts) };
serde_json::to_string(&response).unwrap()
}
#[get("/post/<post_id>")]
pub fn list_post_handler(post_id: i32) -> Result<String, NotFound<String>> {
// 👇
New function body!
let post = read::list_post(post_id)?;
let response = Response { body: ResponseBody::Post(post) };
Ok(serde_json::to_string(&response).unwrap())
}
Congratulations! You’ve just written your first two routes, hooked them up to a
database, and have them both successfully reading content from it. Unfortunately,
there isn’t much to read yet as there are no blog posts in our table.
Creating Posts
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 14/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
Like before, we’ll start by templating out the route handler. This will be a POST
request that will accept JSON data.
// api/src/post_handler.rs
// ...
With that done, we can start the implementation of the create_post() function.
// application/src/post/mod.rs
// application/src/post/create.rs
match diesel::insert_into(posts::table).values(&post).get_result::<Post>(&m
Ok(post) => {
let response = Response { body: ResponseBody::Post(post) };
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 15/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
Created::new("").tagged_body(serde_json::to_string(&response).unwra
},
Err(err) => match err {
_ => {
panic!("Database error - {}", err);
}
}
}
}
// api/src/bin/main.rs
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/api", routes![
post_handler::list_posts_handler,
post_handler::list_post_handler,
post_handler::create_post_handler, // 👈 New!
])
}
Now that’s done, lets finally test the API with some data!
CR__ Testing
With two of our four letters implemented, let’s give it a small test run. Navigate back
to the root directory and run the application.
cargo run
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 16/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
After the project has built, open up your favourite API testing tool and check the
routes work as expected.
Like with the past two letters, let’s create our handlers.
// api/src/post_handler.rs
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 17/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
// ...
#[get("/publish/<post_id>")]
pub fn publish_post_handler(post_id: i32) -> Result<String, NotFound<String>> {
let post = publish::publish_post(post_id)?;
let response = Response { body: ResponseBody::Post(post) };
Ok(serde_json::to_string(&response).unwrap())
}
#[get("/delete/<post_id>")]
pub fn delete_post_handler(post_id: i32) -> Result<String, NotFound<String>> {
let posts = delete::delete_post(post_id)?;
let response = Response { body: ResponseBody::Posts(posts) };
Ok(serde_json::to_string(&response).unwrap())
}
// application/src/post/mod.rs
// application/src/post/publish.rs
use domain::models::Post;
use shared::response_models::{Response, ResponseBody};
use infrastructure::establish_connection;
use rocket::response::status::NotFound;
use diesel::prelude::*;
match diesel::update(posts.find(post_id)).set(published.eq(true)).get_resul
Ok(post) => Ok(post),
Err(err) => match err {
diesel::result::Error::NotFound => {
let response = Response { body: ResponseBody::Message(format!("
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 18/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
return Err(NotFound(serde_json::to_string(&response).unwrap()))
},
_ => {
panic!("Database error - {}", err);
}
}
}
}
// application/src/post/delete.rs
if num_deleted > 0 {
match posts::table.select(posts::all_columns).load::<Post>(&mut establi
Ok(mut posts_) => {
posts_.sort();
Ok(posts_)
},
Err(err) => match err {
_ => {
panic!("Database error - {}", err);
}
}
}
} else {
response = Response { body: ResponseBody::Message(format!("Error - no p
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 19/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
Err(NotFound(serde_json::to_string(&response).unwrap()))
}
}
// api/src/bin/main.rs
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/api", routes![
post_handler::list_posts_handler,
post_handler::list_post_handler,
post_handler::create_post_handler,
post_handler::publish_post_handler, // 👈 New!
post_handler::delete_post_handler, // 👈 New!
])
}
And that’s it! You now have a fully functioning API written in Rocket.rs, which
connects to a PostgreSQL database via Diesel.rs. Not only that, but the application is
structured following Clean Architecture.
.
├── Cargo.lock
├── Cargo.toml
├── api
│ ├── Cargo.toml
│ └── src
│ ├── bin
│ │ └── main.rs
│ ├── lib.rs
│ └── post_handler.rs
├── application
│ ├── Cargo.toml
│ └── src
│ ├── lib.rs
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 20/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
│ └── post
│ ├── create.rs
│ ├── delete.rs
│ ├── mod.rs
│ ├── publish.rs
│ └── read.rs
├── domain
│ ├── Cargo.toml
│ └── src
│ ├── lib.rs
│ ├── models.rs
│ └── schema.rs
├── infrastructure
│ ├── Cargo.toml
│ ├── migrations
│ │ └── 2022–11–18–090125_create_posts
│ │ ├── up.sql
│ │ └── down.sql
│ └── src
│ └── lib.rs
└── shared
├── Cargo.toml
└── src
├── lib.rs
└── response_models.rs
Further Improvements
There are a few things that could be improved when looking at the application as a
whole.
Firstly, whenever we want to use the database we open up a new connection. This
Open in app
can become costly and resource intensive when on a larger scale. One way this
could be fixed is by using a connection pool, Rocket.rs includes built in support for
R2D2, a connection pool handler for Rust.
Secondly, Diesel.rs is not asynchronous — this isn’t too much of an issue on this
scale. However, it can become a bigger problem for larger applications. There is, at
the time writing, no asynchronous implementation from the official team behind
Diesel.rs. As an alternative, an external crate is available to provide this
functionality.
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 21/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
Finally, a front-end UI could be created alongside the Rust API. Inside the root
directory you would create a new project called web_ui using your front-end
language of choice. All you’d then need to do is run both projects separately, calling
the Rust API from your front-end client. Here’s my implementation of a front-end
for some inspiration:
Conclusion
Phew! What a journey. Not only have we learnt how to use Rocket.rs and Diesel.rs
but we’ve learnt how to use them together to create a blogging API in Rust. Along
with that, we’ve built a front-end for it and packaged it all together in a single project
file following Clean Architecture.
All code along with my implementation of the front-end can be found here:
https://github.com/BrookJeynes/blog-rust
I hope you guys learnt a lot today, and give the process a go yourself and create
something new! Make sure to star the Github repository and let me know what I
should cover next or any feedback you have.
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 22/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
References
Crates:
Diesel.rs
Rocket.rs
Serde-rs/serde
Serde-rs/json
A Simple Crud on Rust (With Rocket.rs and Diesel.rs) (Uses deprecated packages
for current rust version (v1.65.0) 🙁)
Too many Stackoverflow posts to mention
Follow
Hi, I'm Brook, an aussie software developer specialising in Typescript, Rust, and .NET
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 23/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
Brook Jeynes
52
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 24/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
Brook Jeynes
106
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 25/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
3.8K 34
Luca Corsetti
39 1
Lists
New_Reading_List
174 stories · 50 saves
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 26/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
Mabrouk Mahdhi
18
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 27/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
7.1K 53
Juliano Silva
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 28/29
8/4/23, 11:18 PM Building an API in Rust with Rocket.rs and Diesel.rs (Clean Architecture) | by Brook Jeynes | Medium
277 4
https://medium.com/@jeynesbrook/building-an-api-in-rust-with-rocket-rs-and-diesel-rs-clean-architecture-8f6092ee2606 29/29