REST API has become the De Facto for connecting and transferring data from one source to another. It offers a set of guidelines and architectural patterns for designing and developing web services.
This post will discuss building a user management application with Rust using the Rocket framework and MongoDB. At the end of this tutorial, we will learn how to structure a Rust application, build a REST API and persist our data using MongoDB.
Rocket is an HTTP web framework written in Rust with priorities on security, flexibility, and speed. Rocket ships with features like type-safety, reusability, logging, static file serving and much more that developers can leverage to build scalable applications with less code.
MongoDB is a document-based database management program used as an alternative to relational databases. MongoDB supports working with large sets of distributed data with options to store or retrieve information seamlessly.
The complete source code is available in this repository.
Prerequisites
To fully grasp the concepts presented in this tutorial, experience with Rust is required. Experience with MongoDB isn’t a requirement, but it’s nice to have.
We will also be needing the following:
- A MongoDB account to host database. Signup is completely free.
- Postman or any API testing application
Let’s code
Getting Started
To get started, we need to navigate to the desired directory and run the command below in our terminal
cargo new rocket-mongo-api && cd rocket-mongo-api
This command creates a Rust project called rocket-mongo-api
and navigates into the project directory.
Next, we proceed to install the required dependencies by modifying the [dependencies]
section of the Cargo.toml
file as shown below:
//other code section goes here
[dependencies]
rocket = {version = "0.5.0-rc.2", features = ["json"]}
serde = "1.0.136"
dotenv = "0.15.0"
[dependencies.mongodb]
version = "2.2.0"
default-features = false
features = ["sync"]
rocket = {version = "0.5.0-rc.2", features = ["json"]}
is a Rust-based framework for building web applications. It also specifies the required version and the feature type(json).
serde = "1.0.136"
is a framework for serializing and deserializing Rust data structures. E.g. convert Rust structs to JSON.
dotenv = "0.15.0"
is a library for managing environment variables.
[dependencies.mongodb]
is a driver for connecting to MongoDB. It also specifies the required version and the feature type(Sync API).
We need to run the command below to install the dependencies:
cargo build
Application Entry Point
With the project dependencies installed, modify the main.rs
file in the src
folder to the following:
#[macro_use]
extern crate rocket;
use rocket::{get, http::Status, serde::json::Json};
#[get("/")]
fn hello() -> Result<Json<String>, Status> {
Ok(Json(String::from("Hello from rust and mongoDB")))
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![hello])
}
The snippet above does the following:
- Imports the required dependencies
- Creates a
hello
handler that uses the Rust macro to specify the HTTP method, the route path/
, and returns a JSON ofHello from rust and mongoDB
. - Uses the
#launch
macro to run themain
function asynchronously and starts the server. Themain
function creates a new server using thebuild
function and mounts thehello
handler to a route.
Next, we can test our application by running the command below in our terminal.
cargo run
Rocket runs the development server on http://127.0.0.1:8000
or localhost:8000
.
Module system in Rust
A module in Rust is a mechanism for splitting code into reusable components and managing visibility between them. Modules help us maintain a good project structure for our project.
To do this, we need to navigate to the src
folder and create api
, models
, and repository
folder with the corresponding mod.rs
file to manage visibility.
api
is for modularizing API handlers.
models
is for modularizing data logics.
repository
is for modularizing database logics.
Adding reference to the Modules
To use the code in the modules, we need to declare them as a module and import them into the main.rs
file.
//add the modules
mod api;
mod models;
mod repository;
#[macro_use]
extern crate rocket;
use rocket::{get, http::Status, serde::json::Json};
// the remaining part of our code goes here
Setting up MongoDB
With that done, we need to log in or sign up into our MongoDB account. Click the project dropdown menu and click on the New Project button.
Enter the rust-api
as the project name, click Next, and click Create Project..
Click on Build a Database
Select Shared as the type of database.
Click on Create to setup a cluster. This might take sometime to setup.
Next, we need to create a user to access the database externally by inputting the Username, Password and then clicking on Create User. We also need to add our IP address to safely connect to the database by clicking on the Add My Current IP Address button. Then click on Finish and Close to save changes.
On saving the changes, we should see a Database Deployments screen, as shown below:
Connecting our application to MongoDB
With the configuration done, we need to connect our application with the database created. To do this, click on the Connect button
Click on Connect your application, change the Driver to Rust
and the Version as shown below. Then click on the copy icon to copy the connection string.
Setup Environment Variable
Next, we must modify the copied connection string with the user's password we created earlier and change the database name. To do this, first, we need to create a .env
file in the root directory, and in this file, add the snippet copied:
MONGOURI=mongodb+srv://<YOUR USERNAME HERE>:<YOUR PASSWORD HERE>@cluster0.e5akf.mongodb.net/myFirstDatabese?retryWrites=true&w=majority
Sample of a properly filled connection string below:
MONGOURI=mongodb+srv://malomz:malomzPassword@cluster0.e5akf.mongodb.net/golangDB?retryWrites=true&w=majority
Creating REST APIs
With the setup done, we need to create a model to represent our application data. To do this, we need to navigate to the models
folder, and in this folder, create a user_model.rs
file and add the snippet below:
use mongodb::bson::oid::ObjectId;
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<ObjectId>,
pub name: String,
pub location: String,
pub title: String,
}
The snippet above does the following:
- Imports the required dependencies
- Uses the
derive
macro to generate implementation support for formatting the output, serializing, and deserializing the data structure. - Creates a
User
struct with required properties. We also added field attributes to theid
property to rename and ignore the field if it is empty.
PS: The pub
modifier makes the struct and its property public and can be accessed from other files/modules.
Next, we must register the user_model.rs
file as part of the models
module. To do this, open the mod.rs
in the models
folder and add the snippet below:
pub mod user_model;
Create a User Endpoint
With the model fully set up and made available to be consumed, we can now create our database logic to create a user. To do this, First, we need to navigate to the repository
folder, and in this folder, create a mongodb_repo.rs
file and add the snippet below:
use std::env;
extern crate dotenv;
use dotenv::dotenv;
use mongodb::{
bson::{extjson::de::Error},
results::{ InsertOneResult},
sync::{Client, Collection},
};
use crate::models::user_model::User;
pub struct MongoRepo {
col: Collection<User>,
}
impl MongoRepo {
pub fn init() -> Self {
dotenv().ok();
let uri = match env::var("MONGOURI") {
Ok(v) => v.to_string(),
Err(_) => format!("Error loading env variable"),
};
let client = Client::with_uri_str(uri).unwrap();
let db = client.database("rustDB");
let col: Collection<User> = db.collection("User");
MongoRepo { col }
}
pub fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
let new_doc = User {
id: None,
name: new_user.name,
location: new_user.location,
title: new_user.title,
};
let user = self
.col
.insert_one(new_doc, None)
.ok()
.expect("Error creating user");
Ok(user)
}
}
The snippet above does the following:
- Imports the required dependencies
- Creates a
MongoRepo
struct with acol
field to access MongoDB collection - Creates an implementation block that adds methods to the
MongoRepo
struct - Adds an
init
method to the implementation block to load the environment variable, creates a connection to the database, and returns an instance of theMongoRepo
struct - Adds a
create_user
method that takes in aself
andnew_user
as parameters and returns the created user or an error. Inside the method, we created a new document using theUser
struct. Then we use theself
referencing theMongoRepo
struct to access theinsert_one
function from the collection to create a new user and handle errors. Finally, we returned the created user information.
PS: The None
specified when creating a new document tells MongoDB to automatically generate the user’s id.
Next, we must register the mongodb_repo.rs
file as part of the repository
module. To do this, open the mod.rs
in the repository
folder and add the snippet below:
pub mod mongodb_repos;
Secondly, we need to create a handler that uses the create_user
method from the repository
to create a user. To do this, we need to navigate to the api
folder, and in this folder, create a user_api.rs
file and add the snippet below:
use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use mongodb::results::InsertOneResult;
use rocket::{http::Status, serde::json::Json, State};
#[post("/user", data = "<new_user>")]
pub fn create_user(
db: &State<MongoRepo>,
new_user: Json<User>,
) -> Result<Json<InsertOneResult>, Status> {
let data = User {
id: None,
name: new_user.name.to_owned(),
location: new_user.location.to_owned(),
title: new_user.title.to_owned(),
};
let user_detail = db.create_user(data);
match user_detail {
Ok(user) => Ok(Json(user)),
Err(_) => Err(Status::InternalServerError),
}
}
The snippet above does the following:
- Imports the required dependencies
- Uses the routing macro to specify HTTP method, route, and indicates that the handler expects body data
- Creates a
create_user
handler that takes in thedb
, a type to theMongoRepo
and anew_user
as parameters. Inside the handler, we created adata
variable for creating a user, inserted it into the database using thedb.create_user
method, and returned the correct response if the insert was successful or error if any.
PS: The &State
and Json
struct used in defining the parameter is for managing application state shared across routes and extracting JSON data from request payloads, respectively.
Finally, we need to modify our application entry point to include the create_user
handler. To do this, we need to navigate to the main.rs
file and modify it as shown below:
mod api;
mod models;
mod repository;
#[macro_use]
extern crate rocket;
//add imports below
use api::user_api::create_user;
use repository::mongodb_repo::MongoRepo;
#[launch]
fn rocket() -> _ {
let db = MongoRepo::init();
rocket::build().manage(db).mount("/", routes![create_user])
}
The snippet above does the following:
- Imports the required dependencies
- Creates a
db
variable to establish a connection to MongoDB by calling theinit()
method and adds it to themanage
function to make the database state available across the application scope - Uses the
app_data
andservice
function to add the application data and the handler to theApp
instance
Get a User Endpoint
To get the details of a user, we must first modify the mongodb_repo.rs
file by adding a get_user
method to the implementation block.
use std::env;
extern crate dotenv;
use dotenv::dotenv;
use mongodb::{
bson::{extjson::de::Error, oid::ObjectId, doc}, //modify here
results::{ InsertOneResult},
sync::{Client, Collection},
};
use crate::models::user_model::User;
pub struct MongoRepo {
col: Collection<User>,
}
impl MongoRepo {
pub fn init() -> Self {
//init code goes here
}
pub fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
//create_user code goes here
}
pub fn get_user(&self, id: &String) -> Result<User, Error> {
let obj_id = ObjectId::parse_str(id).unwrap();
let filter = doc! {"_id": obj_id};
let user_detail = self
.col
.find_one(filter, None)
.ok()
.expect("Error getting user's detail");
Ok(user_detail.unwrap())
}
}
The snippet above does the following:
- Modifies the dependencies to include
oid::ObjectId
anddoc
- Adds a
get_user
method that takes in aself
andid
as parameters and returns the user detail or an error. Inside the method, we converted theid
to anObjectId
and used it as a filter to get matching document. Then we use theself
referencing theMongoRepo
struct to access thefind_one
function from the collection to get the details of the user and handle errors. Finally, we returned the created user information.
Secondly, we need to modify user_api.rs
by creating a handler that uses the get_user
method from the repository
to get a user.
use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use mongodb::results::InsertOneResult;
use rocket::{http::Status, serde::json::Json, State};
#[post("/user", data = "<new_user>")]
pub fn create_user(
db: &State<MongoRepo>,
new_user: Json<User>,
) -> Result<Json<InsertOneResult>, Status> {
//create_user code goes here
}
#[get("/user/<path>")]
pub fn get_user(db: &State<MongoRepo>, path: String) -> Result<Json<User>, Status> {
let id = path;
if id.is_empty() {
return Err(Status::BadRequest);
};
let user_detail = db.get_user(&id);
match user_detail {
Ok(user) => Ok(Json(user)),
Err(_) => Err(Status::InternalServerError),
}
}
The snippet above does the following:
- Uses the routing macro to specify HTTP method, corresponding route and route parameter
- Creates a
get_user
handler that takes in thedb
, a type to theMongoRepo
and apath
for accessing route path as parameters. Inside the handler, we created anid
variable to get the user’s id, get the user’s details from the database using thedb.get_user
method. We returned the correct response if the request was successful or error if any.
Finally, we need to modify our application entry point(main.rs
)to include the get_user
handler by importing the handler and adding a new service for it.
mod api;
mod models;
mod repository;
#[macro_use]
extern crate rocket;
use api::user_api::{create_user, get_user}; //import the handler here
use repository::mongodb_repo::MongoRepo;
#[launch]
fn rocket() -> _ {
let db = MongoRepo::init();
rocket::build()
.manage(db)
.mount("/", routes![create_user])
.mount("/", routes![get_user])
}
Edit a User Endpoint
To edit a user, we must first modify the mongodb_repo.rs
file by adding an edit_user
method to the implementation block.
use std::env;
extern crate dotenv;
use dotenv::dotenv;
use mongodb::{
bson::{extjson::de::Error, oid::ObjectId, doc},
results::{ InsertOneResult, UpdateResult}, //modify here
sync::{Client, Collection},
};
use crate::models::user_model::User;
pub struct MongoRepo {
col: Collection<User>,
}
impl MongoRepo {
pub fn init() -> Self {
//init code goes here
}
pub fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
//create_user code goes here
}
pub fn get_user(&self, id: &String) -> Result<User, Error> {
//get_user code goes here
}
pub fn update_user(&self, id: &String, new_user: User) -> Result<UpdateResult, Error> {
let obj_id = ObjectId::parse_str(id).unwrap();
let filter = doc! {"_id": obj_id};
let new_doc = doc! {
"$set":
{
"id": new_user.id,
"name": new_user.name,
"location": new_user.location,
"title": new_user.title
},
};
let updated_doc = self
.col
.update_one(filter, new_doc, None)
.ok()
.expect("Error updating user");
Ok(updated_doc)
}
}
The snippet above does the following:
- Modifies the dependencies to include
UpdateResult
- Adds an
update_user
method that takes in aself
,id
, andnew_user
parameters and returns the updated user detail or an error. Inside the method, we converted theid
to anObjectId
, created afilter
variable to get the matching document we wanted to update and used thedoc
macro to update the document fields. Then we use theself
referencing theMongoRepo
struct to access theupdate_one
function from the collection to update the user matching thefilter
specified and handle errors. Finally, we returned the updated user information.
Secondly, we need to modify user_api.rs
by creating a handler that uses the update_user
method from the repository
to update a user.
use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use mongodb::{bson::oid::ObjectId, results::InsertOneResult}; //modify here
use rocket::{http::Status, serde::json::Json, State};
#[post("/user", data = "<new_user>")]
pub fn create_user(
db: &State<MongoRepo>,
new_user: Json<User>,
) -> Result<Json<InsertOneResult>, Status> {
//create_user code goes here
}
#[get("/user/<path>")]
pub fn get_user(db: &State<MongoRepo>, path: String) -> Result<Json<User>, Status> {
//get_user code goes here
}
#[put("/user/<path>", data = "<new_user>")]
pub fn update_user(
db: &State<MongoRepo>,
path: String,
new_user: Json<User>,
) -> Result<Json<User>, Status> {
let id = path;
if id.is_empty() {
return Err(Status::BadRequest);
};
let data = User {
id: Some(ObjectId::parse_str(&id).unwrap()),
name: new_user.name.to_owned(),
location: new_user.location.to_owned(),
title: new_user.title.to_owned(),
};
let update_result = db.update_user(&id, data);
match update_result {
Ok(update) => {
if update.matched_count == 1 {
let updated_user_info = db.get_user(&id);
return match updated_user_info {
Ok(user) => Ok(Json(user)),
Err(_) => Err(Status::InternalServerError),
};
} else {
return Err(Status::NotFound);
}
}
Err(_) => Err(Status::InternalServerError),
}
}
The snippet above does the following:
- Modifies the dependencies to include
ObjectId
- Uses the routing macro to specify HTTP method, corresponding route, route parameter, and body data
- Creates an
update_user
handler that takes in thedb
, a type to theMongoRepo
,path
, andnew_user
as parameters. Inside the handler, we created anid
variable to get the user’s id, update the user’s details from the database using thedb.update_user
method by passing in the updated user’s information. Finally, we checked if the update was successful and returned the updated user or error if any.
Finally, we need to modify our application entry point(main.rs
)to include the update_user
handler by importing the handler and adding a new service for it.
mod api;
mod models;
mod repository;
#[macro_use]
extern crate rocket;
use api::user_api::{create_user, get_user, update_user}; //import the handler here
use repository::mongodb_repo::MongoRepo;
#[launch]
fn rocket() -> _ {
let db = MongoRepo::init();
rocket::build()
.manage(db)
.mount("/", routes![create_user])
.mount("/", routes![get_user])
.mount("/", routes![update_user])
}
Delete a User Endpoint
To delete a user, we must first modify the mongodb_repo.rs
file by adding an delete_user
method to the implementation block.
use std::env;
extern crate dotenv;
use dotenv::dotenv;
use mongodb::{
bson::{extjson::de::Error, oid::ObjectId, doc},
results::{ InsertOneResult, UpdateResult, DeleteResult}, //modify here
sync::{Client, Collection},
};
use crate::models::user_model::User;
pub struct MongoRepo {
col: Collection<User>,
}
impl MongoRepo {
pub fn init() -> Self {
//init code goes here
}
pub fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
//create_user code goes here
}
pub fn get_user(&self, id: &String) -> Result<User, Error> {
//get_user code goes here
}
pub fn update_user(&self, id: &String, new_user: User) -> Result<UpdateResult, Error> {
//update_user code goes here
}
pub fn delete_user(&self, id: &String) -> Result<DeleteResult, Error> {
let obj_id = ObjectId::parse_str(id).unwrap();
let filter = doc! {"_id": obj_id};
let user_detail = self
.col
.delete_one(filter, None)
.ok()
.expect("Error deleting user");
Ok(user_detail)
}
}
The snippet above does the following:
- Modifies the dependencies to include
DeleteResult
- Adds a
delete_user
method that takes in aself
andid
as parameters and returns the deleted user detail or an error. Inside the method, we converted theid
to anObjectId
and created afilter
variable to get the matching document we wanted to delete. Then we use theself
referencing theMongoRepo
struct to access thedelete_one
function from the collection to delete the user matching thefilter
specified and handle errors. Finally, we returned the deleted user information.
Secondly, we need to modify user_api.rs
by creating a handler that uses the delete_user
method from the repository
to delete a user.
use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use mongodb::{bson::oid::ObjectId, results::InsertOneResult}; //modify here
use rocket::{http::Status, serde::json::Json, State};
#[post("/user", data = "<new_user>")]
pub fn create_user(
db: &State<MongoRepo>,
new_user: Json<User>,
) -> Result<Json<InsertOneResult>, Status> {
//create_user code goes here
}
#[get("/user/<path>")]
pub fn get_user(db: &State<MongoRepo>, path: String) -> Result<Json<User>, Status> {
//get_user code goes here
}
#[put("/user/<path>", data = "<new_user>")]
pub fn update_user(
db: &State<MongoRepo>,
path: String,
new_user: Json<User>,
) -> Result<Json<User>, Status> {
//update_user code goes here
}
#[delete("/user/<path>")]
pub fn delete_user(db: &State<MongoRepo>, path: String) -> Result<Json<&str>, Status> {
let id = path;
if id.is_empty() {
return Err(Status::BadRequest);
};
let result = db.delete_user(&id);
match result {
Ok(res) => {
if res.deleted_count == 1 {
return Ok(Json("User successfully deleted!"));
} else {
return Err(Status::NotFound);
}
}
Err(_) => Err(Status::InternalServerError),
}
}
The snippet above does the following:
- Uses the routing macro to specify HTTP method, corresponding route and route parameter
- Creates a
delete_user
handler that takes in thedb
, a type to theMongoRepo
andpath
as parameters. Inside the handler, we created anid
variable to get the user’s id and delete the user from the database using thedb.delete_user
method by passing in theid
. Finally, we returned the appropriate response or error if any.
Finally, we need to modify our application entry point(main.rs
)to include the delete_user
handler by importing the handler and adding a new service for it.
mod api;
mod models;
mod repository;
#[macro_use]
extern crate rocket;
use api::user_api::{create_user, get_user, update_user, delete_user}; //import the handler here
use repository::mongodb_repo::MongoRepo;
#[launch]
fn rocket() -> _ {
let db = MongoRepo::init();
rocket::build()
.manage(db)
.mount("/", routes![create_user])
.mount("/", routes![get_user])
.mount("/", routes![update_user])
.mount("/", routes![delete_user])
}
Get all Users Endpoint
To get the list of users, we must first modify the mongodb_repo.rs
file by adding a get_all_users
method to the implementation block.
use std::env;
extern crate dotenv;
use dotenv::dotenv;
use mongodb::{
bson::{extjson::de::Error, oid::ObjectId, doc},
results::{ InsertOneResult, UpdateResult, DeleteResult},
sync::{Client, Collection},
};
use crate::models::user_model::User;
pub struct MongoRepo {
col: Collection<User>,
}
impl MongoRepo {
pub fn init() -> Self {
//init code goes here
}
pub fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
//create_user code goes here
}
pub fn get_user(&self, id: &String) -> Result<User, Error> {
//get_user code goes here
}
pub fn update_user(&self, id: &String, new_user: User) -> Result<UpdateResult, Error> {
//update_user code goes here
}
pub fn delete_user(&self, id: &String) -> Result<DeleteResult, Error> {
//delete_user code goes here
}
pub fn get_all_users(&self) -> Result<Vec<User>, Error> {
let cursors = self
.col
.find(None, None)
.ok()
.expect("Error getting list of users");
let users = cursors.map(|doc| doc.unwrap()).collect();
Ok(users)
}
}
The snippet above adds a get_all_users
method that takes in a self
as a parameter and returns the list of users or an error. Inside the method, we use the self
referencing the MongoRepo
struct to access the find
function from the collection without any filter so that it can match all the documents inside the database and handle errors. Finally, we returned the list of users.
Secondly, we need to modify user_api.rs
by creating a handler that uses the get_all_users
method from the repository
to get list of users.
use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use mongodb::{bson::oid::ObjectId, results::InsertOneResult};
use rocket::{http::Status, serde::json::Json, State};
#[post("/user", data = "<new_user>")]
pub fn create_user(
db: &State<MongoRepo>,
new_user: Json<User>,
) -> Result<Json<InsertOneResult>, Status> {
//create_user code goes here
}
#[get("/user/<path>")]
pub fn get_user(db: &State<MongoRepo>, path: String) -> Result<Json<User>, Status> {
//get_user code goes here
}
#[put("/user/<path>", data = "<new_user>")]
pub fn update_user(
db: &State<MongoRepo>,
path: String,
new_user: Json<User>,
) -> Result<Json<User>, Status> {
//update_user code goes here
}
#[delete("/user/<path>")]
pub fn delete_user(db: &State<MongoRepo>, path: String) -> Result<Json<&str>, Status> {
//delete_user code goes here
}
#[get("/users")]
pub fn get_all_users(db: &State<MongoRepo>) -> Result<Json<Vec<User>>, Status> {
let users = db.get_all_users();
match users {
Ok(users) => Ok(Json(users)),
Err(_) => Err(Status::InternalServerError),
}
}
The snippet above does the following:
- Uses the routing macro to specify HTTP method and corresponding route
- Creates a
get_all_users
handler that uses thedb.delete_user
method to get the list of users. Then, we returned the list of users or error if any.
Finally, we need to modify our application entry point(main.rs
)to include the get_all_users
handler by importing the handler and adding a new service for it.
mod api;
mod models;
mod repository;
#[macro_use]
extern crate rocket;
use api::user_api::{create_user, get_user, update_user, delete_user, get_all_users}; //import the handler here
use repository::mongodb_repo::MongoRepo;
#[launch]
fn rocket() -> _ {
let db = MongoRepo::init();
rocket::build()
.manage(db)
.mount("/", routes![create_user])
.mount("/", routes![get_user])
.mount("/", routes![update_user])
.mount("/", routes![delete_user])
.mount("/", routes![get_all_users])
}
With that done, we can test our application by running the command below in our terminal.
cargo run
Conclusion
This post discussed how to modularize a Rust application, build a REST API, and persist our data using MongoDB.
You may find these resources helpful: