

How to Build a Simple API in Rust (Part 2)
source link: https://hackernoon.com/how-to-build-a-simple-api-in-rust-part-2-vv9q35ze
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

How to Build a Simple API in Rust (Part 2)
















@ncribtNARUHODO
Software engineer during the week and apprentice blogger during the weekend. I like JS and Rust. 🤓
Welcome to the second part of the guide on how to build an API in Rust.




To follow this guide, you will need to have the code from the first part. If you haven’t checked it out yet, please do!




This time I will explain how to connect the API to MongoDB. I will create two endpoints, one to add data to the database and another one to retrieve the data from the database.




I will put all the code in the same file (
src/main.rs
). Of course, in a real situation, you should split the code into multiple files/directories and separate the logic between controllers and services.



Prerequisites
To be able to follow the guide, you will need to have Rust and Cargo installed. You will also need to have MongoDB running. If you’re not sure how to install MongoDB, there are 3 ways I can think of:




1. You can install MongoDB on your machine. Follow the official documentation to do so.




2. If you have Docker, you can run a MongoDB container. Check out the mongo image documentation. Th.is is the command I use to run the mongo container:




docker run -d -p 27017:27017 --name mongo mongo
3. If you prefer to avoid installing anything (MongoDB/Docker), you can create a free cluster on MongoDB Atlas.




Set up the environment
We need to create aÂ
.env
file to store the MongoDB connection string safely. If you're using Git, make sure to add .env
in your .gitignore
. You should never commit a .env
file.



Open the project from part 1 and create theÂ
.env
file.



touch .env
Inside the .env file, add the following:




MONGODB_URI=mongodb://localhost:27017
Make sure to replace the connection string with the correct one depending on how you’re running MongoDB.




It is good practice to have aÂ
.env.example
file (that one should be committed) so that when someone pulls the project for the first time, they have an example of how to set up the .env
file.



Create a .env.example file




touch .env.example
Add the following:




MONGODB_URI= ** MongoDB URI (e.g. mongodb://localhost:27017)
Add the new dependencies
We need to add 2 new dependencies:
dotenv
and mongodb
.



dotenv
is a crate that allows us to use a .env file. It adds the variables from the file to the environment variables.



mongodb
is the official MongoDB Rust driver. It allows us to interact with the database.



Update the
Cargo.toml
file with the two dependencies. The updated dependencies should be like this:



[dependencies]
tide = "0.16"
async-std = { version = "1", features = ["attributes"] }
serde = { version = "1", features = ["derive"] }
dotenv = "0.15"
mongodb = { version = "1", features = ["async-std-runtime"], default-features = false }
Because we use the
async-std
runtime, we need to disable the default features of the mongodb crate. By default, it uses tokio
. Then we need to add the async-std-runtime
feature to use the async-std
runtime.



Let’s code
We need to make some modifications to the code. First, update the imports with the following:




use async_std::stream::StreamExt;
use dotenv::dotenv;
use mongodb::bson::doc;
use serde::{Deserialize, Serialize};
use std::env;
use tide::{Body, Request, Response, StatusCode};
These are all the module items we will use in this walk-through.




Remember last time we set up an empty Tide
State
? This time we will use the State
to pass the database connection to the controllers. We need to update the State
struct like this:



#[derive(Clone)]
struct State {
db: mongodb::Database,
}
Now we need to update the
main
function. We have to create the database connection and add it in the state before creating the Tide app.



Here is the updated
main
function:



#[async_std::main]
async fn main() -> tide::Result<()> {
// Use the dotenv crate to read the .env file and add the environment variables
dotenv().ok();
// Create the MongoDB client options with the connection string from the environment variables
let mongodb_client_options =
mongodb::options::ClientOptions::parse(&env::var("MONGODB_URI").unwrap()).await?;
// Instantiate the MongoDB client
let mongodb_client = mongodb::Client::with_options(mongodb_client_options)?;
// Get a handle to the "rust-api-example" database
let db = mongodb_client.database("rust-api-example");
// Create the Tide state with the database connection
let state = State { db };
let mut app = tide::with_state(state);
app.at("/hello").get(hello);
app.listen("127.0.0.1:8080").await?;
return Ok(());
}
I’ve added comments in the code to help you understand what’s going on.




I first called the
dotenv().ok()
method to read from the .env
file and add the environment variables. Then I created a connection to the database. Finally, I passed that connection to the Tide State
so that our controllers have access to it.



We will now create two controllers, the first one to create documents in the database and the second one to retrieve those documents.




Here is the code of the first controller:




#[derive(Debug, Serialize, Deserialize)]
// The request's body structure
pub struct Item {
pub name: String,
pub price: f32,
}
async fn post_item(mut req: Request<State>) -> tide::Result {
// Read the request's body and transform it into a struct
let item = req.body_json::<Item>().await?;
// Recover the database connection handle from the Tide state
let db = &req.state().db;
// Get a handle to the "items" collection
let items_collection = db.collection_with_type::<Item>("items");
// Insert a new Item in the "items" collection using values
// from the request's body
items_collection
.insert_one(
Item {
name: item.name,
price: item.price,
},
None,
)
.await?;
// Return 200 if everything went fine
return Ok(Response::new(StatusCode::Ok));
}
First, I’ve defined an
Item
struct that represents the request's body. The body should be a JSON object with a property name
of type string
and a property price
of type number
.



Inside the function, I first try to read the request’s body. I did not do any validation. In a real case, don’t be like me and always validate the request’s body before using it.




I recover the database connection from the Tide
State
. Then I get a handle on the items
collection.



Finally, I attempt to insert a new document in the collection using the request’s body values.




If everything goes smoothly, I return an HTTP status code 200.




Now let’s create a second controller to retrieve documents from the
items
collection. Here is the code:



async fn get_items(req: Request<State>) -> tide::Result<tide::Body> {
// Recover the database connection handle from the Tide state
let db = &req.state().db;
// Get a handle to the "items" collection
let items_collection = db.collection_with_type::<Item>("items");
// Find all the documents from the "items" collection
let mut cursor = items_collection.find(None, None).await?;
// Create a new empty Vector of Item
let mut data = Vec::<Item>::new();
// Loop through the results of the find query
while let Some(result) = cursor.next().await {
// If the result is ok, add the Item in the Vector
if let Ok(item) = result {
data.push(item);
}
}
// Send the response with the list of items
return Body::from_json(&data);
}
Inside the function, I retrieve the handle to the database connection from the Tide
State
, then I get a handle to the items
collection.



I use the
find
function to retrieve all the documents from the items
collection. After that, I create a new empty Vector of item.



I loop through the
find
query results, and I insert each item
in the Vector.



Finally, I send the response with the list of items in the body.




The controllers are ready, but we still need to add them to the
main
function. Add the following after the hello
route in the main
function:



app.at("/items").get(get_items);
app.at("/items").post(post_item);
Voilà ! Let’s test it out. Start the server with the following command:




cargo run
You should now be able to send a
POST
request to /items
(http://localhost:8080/items). Here is an example of the body (make sure to set the Content-Type
header to application/json
)



{
"name": "coffee",
"price": 2.5
}
You should get an empty response with status code 200.




Then try to retrieve the document you just created by doing a
GET
request on /items
(http://localhost:8080/items).



You should receive an array with a single entry being the item we just created




[
{
"name": "coffee",
"price": 2.5
}
]
Conclusion
That’s it for part two of this guide. I hope it was helpful. If you noticed any errors or something that could be improved, please let me know!




You can find the full example on GitHub: https://github.com/ncribt/rust-api-example-part-2.




In the next and last part of the guide, I will show you how to protect the POST /items endpoint using a JWT (JSON Web Token) and a middleware.




Previously published on https://naruhodo.dev/build-an-api-in-rust-part-2/.




















Create your free account to unlock your custom reading experience.
Recommend
-
9
PrerequisitesTo be able to follow this guide you should at least know the basics of Rust, if not I would advise checking the Rust Book.
-
5
Build an API in Rust (2 Part Series) In this multi-parts guide I will show you how to build a simple API in Rust. If you're not sure what Rust is, it's a low-leve...
-
11
Build an API in Rust (3 Part Series) Welcome to the third and last part of the guide on how to build an API in Rust! To follow this guide you will need to...
-
8
Build an API in Rust (3 Part Series) Welcome to the second part of the guide on how to build an API in Rust! To follow this guide you will need to have th...
-
24
-
9
Project Debriefing A picture is worth a thousand words. Here's what we're going to build today. Astronomy of the Day Gallery If you haven't read
-
7
Build a Simple API in TypeScript with Nest.jsAs you might know, I work with Nest.js since 2018 when I had to rewrite the backend at my former company Karzo. Ba...
-
6
A Simple REST API with Prisma, Part 1: The Setup This two-part series will cover building a simple REST API using the Prisma ORM and Postgresql. Our API will use the USDA plants data to crea...
-
10
This series covers building a simple REST API using the Prisma ORM and Postgresql. Part one covered how to get a Prisma schema from your Postgresql datab...
-
10
How to build a (simple) blog using Rust So, you have been reading about rust for quite some time, learned that it’s been the most loved programming language multiple years in a row in Stack Overflow’s annual survey and you want in. But, y...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK