Building REST API's with Rust, Actix Web and MongoDB
Tags: rust, mongodb, backend, actixweb
Published: 2022-03-22 18:15:31

---
title: Building REST API's with Rust, Actix Web and MongoDB
published: true
description: 
tags: rust,mongodb, backend, actixweb, backend
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dk2rh76vh2fmsch7z4o0.png
---
Building REST API's with Actix web and Rust

# Introduction

The Rust programming language has been gaining momentum as the most loved programming on the StackOverflow survey for five years, According to Wikipedia it is a multi-paradigm, general-purpose programming language aimed at speed and safety, with a focus on concurrency safety, As a result of this, it used and supported by top tech companies such as Microsoft.

Actix Web is a fast and performant web micro framework used to build restful APIs, In this article, we will explore the actix web framework along with the rust programming language by writing a simple crud API that would demonstrate each of the common HTTP verbs such as POST, GET, PATCH, DELETE.

## Building a REST API

In this article, we will build a simple rest API that showcases each of the HTTP verbs mentioned and implements CRUD. Here are some of the endpoints we would be creating

GET `/todos` - returns a list of todo items

POST `/todos` - create a new todo item

GET `/todos/{id}` - returns one todo

PATCH `/todos/{id}` - updates todo item details

DELETE `/todos/{id}` - delete todo item

### Getting Started

Firstly, we need to have rust installed, you can follow the instructions [here](https://rustup.rs/), Once installed we would initialize an empty project using cargo, Cargo is rust's package manager, similar to npm for Node.js or pip for Python. To create an empty project we run the following command

```txt
cargon init --bin crudapi
```

This command would create a \`Cargo. toml\` file and a src folder. Open the \`Cargo.toml\` file and edit it to add the packages needed. The file should look like this:

```txt
[package]
name = "crudapi"
version = "0.1.0"
edition = "2021"
    
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
[dependencies]
```

After adding the packages the file should look like this:

```txt
    [package]
    name = "crudapi"
    version = "0.1.0"
    edition = "2021"
    
    # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
    [dependencies]
    actix-web = "2.0"
    actix-rt = "1.1.1"
    bson = "1.0.0"
    chrono = "0.4.11"
    futures = "0.3.5"
    MongoDB = "1.0.0"
    rustc-serialize = "0.3.24"
    serde = { version = "1.0", features = ["derive"] }
```

Open the \`main.rs\` file that cargo creates, and import the actix web dependency to use in the file like so

```txt
    use actix_web::{App, HttpServer};
```

We will create five routes in our application to handle the endpoints described. To keep our code well organised, we will put them in a different module called `controllers` and declare it in `main.rs`.

In the `main.rs` we  proceed to create a simple server in our main function which is the entry point of our application

```txt
  // imports
    
 #[actix_rt::main]
    async fn main() -> std::io::Result<()> {
        std::env::set_var("RUST_LOG", "actix_web=debug");
    
        
        HttpServer::new(move || {
            App::new()
                .route("/todos", web::get().to(controllers::get_todos))
                .route("/todos", web::post().to(controllers::create_todo))
                .route("/todos/{id}", web::get().to(controllers::fetch_one))
                .route("/todos/{id}", web::patch().to(controllers::update_todo))
                .route("/todos/{id}", web::delete().to(controllers::delete_todo))
        })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
  }

```

The main function is the entry point for the application which returns a Result type. In the main function, we use the attribute #\[actix\_rt::main] to ensure it’s executed with the actix runtime and proceed to create a new HttpServer instance and also add an App instance to it, add a few routes that point to our \`controllers\` module which would handle the logic for each route and serve it on port 8080.

We proceed to create the \`controllers\` module by creating a simple file inside the \`src\` folder that contains `main.rs` file. Inside the \`controllers\` module, create functions that each route points to like so;

```txt
    // src/controllers.rs
    use actix_web::Responder;
    
    pub async fn get_todos() -> impl Responder {
      format!("fetch all todos");
    }
    
    pub async fn create_todo() ->  impl Responder {
      format!("Creating a new todo item");
    }
    
    pub async fn fetch_one() -> impl Responder {
      format!("Fetch one todo item");
    }
    
    pub async fn update_todo() -> impl Responder {
      format!("Update a todo item");
    }
    
    pub async fn delete_todo() -> impl Responder {
      format!("Delete a todo item");
    }
```

These are the handlers for each route we have specified above, they are each asynchronous functions that return a \`Responder\` trait provided by \`actix-web\`. For now, they return a string, later we would modify each function to implement some logic interacting with a database.

Let’s proceed to run the project:

```txt
cargo run

Finished dev [unoptimized + debuginfo] target(s) in 0.53s
Running `target/debug/crudapi`
```

We can test each endpoint with \`curl\`, in another terminal.

```txt
curl 127.0.0.1:8080/todos
//@returns: fetch all todos
```

## Connect MongoDB Database

We would use the official MongoDB rust crate to allow us to store information in a local database. We initiate the connection in the main function to ensure connection when our server starts running and include it in the app state to be able to pass it into our controllers.

Firstly we import the modules needed in the `main.rs`

```txt
// src/main.rs
   
use MongoDB::{options::ClientOptions, Client};
use std::sync::*;
```

Then proceed to modify the \`main\` function to look like this:

```txt
 // src/main.rs
    
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
        std::env::set_var("RUST_LOG", "actix_web=debug");
        let mut client_options = ClientOptions::parse("MongoDB://127.0.0.1:27017/todolist").await.unwrap();
        client_options.app_name = Some("Todolist".to_string());
        let client = web::Data::new(Mutex::new(Client::with_options(client_options).unwrap()));
        
        HttpServer::new(move || {
            App::new()
                .app_data(client.clone())
                .route("/todos", web::get().to(controllers::get_todos))
                .route("/todos", web::post().to(controllers::create_todo))
                .route("/todos/{id}", web::get().to(controllers::fetch_one))
                .route("/todos/{id}", web::patch().to(controllers::update_todo))
                .route("/todos/{id}", web::delete().to(controllers::delete_todo))
        })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
    } 

```

The above code creates a `MongoDB` client that is wrapped in a `Mutex` for thread safety which is then passed into the app state to be used by our controllers.

## Creating a todo list API

Now that the database connection is ready and in our app state, we proceed to modify our `create_todo` function in our controller to create a new document in the database, firstly you import the modules needed and model the type of data coming as a payload, this can be easily done with structs like so:

```txt
    // src/controllers.rs
    use actix_web::{web, HttpResponse, Responder};
    use MongoDB::{options::FindOptions, Client};
    use bson::{ doc, oid };
    use std::sync::*;
    use futures::stream::StreamExt;
    use serde::{Deserialize, Serialize};
    
    #[derive(Deserialize, Serialize)]
    pub struct Todo {
        pub content: String,
        pub is_done: bool,
    }
    #[derive(Serialize)]
    struct Response {
        message: String,
    }
    
    const MONGO_DB: &'static str = "crudapidb";
    const MONGOCOLLECTION: &'static str = "todo";

```

We imported the needed modules, and created two structs \`Todo\` and \`Response\` and two const variables, The \`Todo\` struct is responsible for how model data would be inputted into the database, and The \`Response\` handles how response messages would be sent back on an endpoint. The \`MONGO\_DB\` and \`MONGOCOLLECTION\` holds the constant strings of our \`database\` name and \`collection\` name.

Now we are ready to create the function that creates a new item in the database

```txt
    // src/controllers.rs
    // imports
    // structs
    // constants
    
    pub async fn create_todo(data: web::Data<Mutex<Client>>, todo: web::Json<Todo>) ->  impl Responder {
      let todos_collection = data.lock().unwrap().database(MONGO_DB).collection(MONGOCOLLECTION);
      match todos_collection.insert_one(doc! {"content": &todo.content, "is_done": &todo.is_done}, None).await {
        Ok(db_result) => {
            if let Some(new_id) = db_result.inserted_id.as_object_id() {
                println!("New document inserted with id {}", new_id);   
            }
            let response = Response {
              message: "Successful".to_string(),
            };
            return HttpResponse::Created().json(response);
        }
        Err(err) =>
        {
            println!("Failed! {}", err);
            return HttpResponse::InternalServerError().finish()
        }
    }
  }

```

This function takes the app state `data` and the payload `todo`app state, firstly we get the todo collection from `MongoDB` client in our app state, then we dynamically create a new document using the `insert_one` function and add the `todo`  payload which returns a `Result` which we use the `match` operator to see if it’s was successful or an error was returned. If it is successful we return a `created` status code \`201\` and success message, else return a `500` internal server error.

### Fetching todo list

Using a get request we can pull out data from our database. According to the routes implemented above, we create two functions to respond to fetching all the todo items in the database and fetching only one from using its id. we modify the \`controllers.rs\` like so:

```txt
  // src/controllers.rs
    
    pub async fn get_todos(data: web::Data<Mutex<Client>>) -> impl Responder {
      let todos_collection = data.lock().unwrap().database(MONGO_DB).collection(MONGOCOLLECTION);
      let filter = doc! {};
      let find_options = FindOptions::builder().sort(doc! { "_id": -1}).build();
      let mut cursor = todos_collection.find(filter, find_options).await.unwrap();
      let mut results = Vec::new();
      while let Some(result) = cursor.next().await {
          match result {
              Ok(document) => {
                  results.push(document);
              }
              _ => {
                  return HttpResponse::InternalServerError().finish();
              }
          }
      }
      HttpResponse::Ok().json(results)
    }
    
    pub async fn fetch_one(data: web::Data<Mutex<Client>>, todo_id: web::Path<String>) -> impl Responder {
      let todos_collection = data.lock().unwrap().database(MONGO_DB).collection(MONGOCOLLECTION);
      
      let filter = doc! {"_id": oid::ObjectId::with_string(&todo_id.to_string()).unwrap() };
      let obj = todos_collection.find_one(filter, None).await.unwrap();
      return HttpResponse::Ok().json(obj);
    } 
```

The `get_todos` function returns all the items in the database using the find function we pass in a `filter` and `find_options`  which sorts the results from newest to oldest, then proceed to iterate the results using the cursor returned by the `find` function, populating the result vector with incoming documents before returning them in json format.

The `fetch_one` function returns a single todo item from the database, the id is passed from the route into the function as a `web::Path`.  The `filter` is passed into the `find_one` function to filter out the item based on the id and it is returned as a response.

### Updating an item in the todo list

The patch request would be responsible for updating an item in the database.

```txt
  // src/controllers.rs
    pub async fn update_todo(data: web::Data<Mutex<Client>>, todo_id: web::Path<String>, todo: web::Json<Todo>) -> impl Responder {
        let todos_collection = data.lock().unwrap().database(MONGO_DB).collection(MONGOCOLLECTION);
        let filter = doc! {"_id": oid::ObjectId::with_string(&todo_id.to_string()).unwrap() };
        let data = doc! { "$set": { "content": &todo.content, "is_done": &todo.is_done } };
        todos_collection.update_one(filter, data, None).await.unwrap();
        
        let response = Response {
            message: "Updated Successfully".to_string(),
          };
        return HttpResponse::Ok().json(response);
  }
```

The `update_todo` accepts the `appstate` and `todo_id` as the id passed from the route and the update `payload`, it filters the document using the id passed and proceeds to update the document in the database and returns a successful message.

### Deleting an item in the todo list

Our final controller deletes an item corresponding to the ID passed to the route.

```txt
// src/controllers.rs
    
pub async fn delete_todo(data: web::Data<Mutex<Client>>, todo_id: web::Path<String>) -> impl Responder {
        let todos_collection = data.lock().unwrap().database(MONGO_DB).collection(MONGOCOLLECTION);
        let filter = doc! {"_id": oid::ObjectId::with_string(&todo_id.to_string()).unwrap() };
        
        todos_collection.delete_one(filter, None).await.unwrap();
        return HttpResponse::NoContent();
}

```

The `delete_one` function filters by id provided in the route and deletes the document from the database, the proceeds to return a \`204\` status code.

## Testing the server

We've successfully built a simple todolist API, now to make client requests. Using `cURL` we can easily test each of the routes in the application.

First, we run the application using the following command

```txt
cargo run
```

Once the server is running open another terminal to test each endpoint.

POST `127.0.0.1:8080/todos` : create an item in the todo list

```txt
$ curl -H "Content-Type: application/json" -XPOST 127.0.0.1:8080/todos -d '{"content": "Read one paragraph of a book", "is_done": false}'
```

This sends a POST request to our /todo endpoint and inserts the payload and associated details into our database. As a response, we receive a success message:

```txt
{"message":"Successful"}
```

GET `127.0.0.1:8080/todos` \:Fetch all items

```txt
$ curl -H "Content-Type: application/json" -XGET 127.0.0.1:8080/todos
```

This returns all the items on our todo list and we get a response like:

```txt
[{"_id":{"$oid":"620d1e64fad81254efb04383"},"content":"Read one paragraph of a book","is_done":false}]
```

GET `127.0.0.1:8080/todos/{id}` : Fetch one item

```txt
$ curl -H "Content-Type: application/json" -XGET 127.0.0.1:8080/todos/620d1e64fad81254efb04383
```

This returns a todo item based on the ID passed into the route and you should get a response like so:

```txt
{"_id":{"$oid":"620d1e64fad81254efb04383"},"content":"Read one paragraph of a book","is_done":false}
```

PATCH `127.0.0.1:8080/todos/{id}` : Update one item

```txt
$ curl -H "Content-Type: application/json" -XPATCH 127.0.0.1:8080/todos/620d1e64fad81254efb04383 -d '{"content":"Read one paragraph of a book", "is_done": true }'
```

This updates the document in the database with the payload sent to it, you should get a success message

```txt
{"message":"Updated Successfully"}             
```

DELETE `127.0.0.1:8080/todos/{id}` : Delete one item

```txt
$ curl -H "Content-Type: application/json" -XDELETE 127.0.0.1:8080/todos/620d1e64fad81254efb04383
```

This removes the item from our database as an empty response without a message because we are returning a `204` status code.

## Conclusion

In conclusion, this article has provided a comprehensive understanding of REST APIs, HTTP verbs, and status codes, along with practical guidance on building a REST API service in Rust, utilizing actix web and MongoDB. As you continue to evolve your application, consider enhancing its functionality by incorporating features such as logging, encryption, rate limiting, and more. These additions will not only enhance security but also contribute to the scalability and overall improvement of your application's performance.