How to build a REST API server with Rust and warp

Rust

In this tutorial, we are going to build a simple Rust REST API using the warp crate. We will learn how to handle simple create, read, update, and delete operations on an in-memory “database”. This tutorial has some similarities with an earlier tutorial that was also using warp.

The full code example for this tutorial can be found on my GitHub: here.

I also have an article explaining how to implement authentication with warp and JSON Web Tokens: JWT security for a Rust REST API: how to.

If you are interested in using WebSockets with warp give it a read: Rust Warp WebSocket server: learn how to now.

Prerequisites

Some basic Rust knowledge is required to follow along.

Some knowledge about REST APIs is a plus. Below is a short summary of REST and its principles.

What is a REST API

REST is an architectural style for building distributed systems serving many different media, such as text, audio, video, etc. This REST-style has some rules and high-level design guidelines. The aim is that by following the guidelines, we can write an API that can be easily and logically understood.

A RESTful system:

  • Has a uniform interface.
  • Is Client-server.
  • Has no state between requests.
  • Can be cached. If the results of a request (say retrieving a list of users) they can be easily cached for a time.

For more detailed information please see this website: https://restfulapi.net/

Typically HTTP request types are used:

  • GET: get information from a resource endpoint. For example: A GET request to /users/ would return a list of users.
  • POST: create a resource. For example: A POST request to /users/, containing the approtioate payload, would create one or more users.
  • PUT: update a resource. For example: A PUT request to /users/1/ would update the user with id 1.
  • DELETE: delete a resource. For example: A DELETE request to /users/1/ would delete the user with id 1.

Project set up and dependencies

We are going to create a REST API using Rust step by step. The rough steps are:

  1. Create project.
  2. Implement barebones Warp server.
  3. Add HashMap to simulate a database.
  4. Then implement the endpoints for Create, Read, Update, and Delete.

First, let’s create the project:

cargo new rust-warp-rest-api-tutorial

Next, we will add the required dependencies to the Cargo.toml file:

[package]
name = "rust-warp-rest-api-tutorial"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tokio = { version= "1", features = ["full"] }
warp = "0.3"
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
uuid = { version = "0.8.2", features = ["serde", "v4"]}

Short descriptions of the crates:

  • tokio: A runtime for writing reliable, asynchronous, and slim applications with the Rust programming language.
  • warp: A super-easy, composable, web server framework for warp speeds.
  • serde: Serde is a framework for serializing and deserializing Rust data structures efficiently and generically.
  • serde_json: A JSON serialization file format.
  • uuid: Generate and parse UUIDs.

With this, we can start to build a Rust REST API.

Basic warp server set up

In this section, we will set up a basic web server for handling HTTP requests using the warp crate. If someone navigates to the webserver address it will show a welcome message by default.

Let’s begin by showing a message “Welcome to my warp server!”, on the root URL path for the server.

use warp::Filter;

#[tokio::main]
async fn main() {
    let root = warp::path::end().map(|| "Welcome to my warp server!");
    let routes = root.with(warp::cors().allow_any_origin());

    warp::serve(routes).run(([127, 0, 0, 1], 5000)).await;
}

Here we have a basic setup where the root route, root, with warp::path::end() and simply return a text. Using warp::path::end() will make this match the root URL, 127.0.0.1:5000 or localhost:5000.

The map part lets us use a simple closure to return a &str result: "Welcome to my warp server!" to the incoming request.

If we run the program now and use a browser to navigate to localhost:5000 or 127.0.0.1:5000, we will see a text saying “Welcome to my warp server!”.

Implementing a “read” shopping list items functionality

In this section, we will build an endpoint for our Rust REST API that will allow a user to retrieve (read) all the data in the database.

Before we can implement code to read data, we have to implement a struct to represent that data. Furthermore, we need something to simulate a database. However, connecting to an actual database is outside of the scope of this tutorial, so we will use an in-memory HashMap instead.

Implementing the shopping list item model

First, let’s create the data model. The model will have the following attributes:

  • id
  • Name
  • Type
  • Description
  • Price

Let’s create it in a new file models.rs:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum ShoppingListItemType {
    Drink,
    Desert,
    Fruit,
    Snack,
    Spread,
    Vegetable,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ShoppingListItem {
    pub item_id: Option<usize>,
    pub name: String,
    pub item_type: ShoppingListItemType,
    pub description: String,
    pub price: f32,
}

Here we have a struct ShoppingListItem with the fields we mentioned earlier. Our struct is annotated with the #[derive()] attribute, which allows new items to be automatically generated for our data structure:

  • Debug allows for easy printing to terminal of the struct.
  • Deserialize is for when we need to deserialize incoming JSON data as a ShoppingListItem struct.
  • Serialize adds functionality to turn our struct into JSON data. For example, this will be used when we send a list of items as response to a GET request.
  • Clone is used when adding objects to the database and returning them as a respone to requests.

The item_id field for our ShoppingListItem is an Option here. Because we will generate the item_id value on the server-side and don’t want the id value sent by the client making a request to create an item, for example.

We have also added an enumeration here called ShoppingListItemType to represent the types of shopping list items. Important here is [serde(rename_all = "lowercase")]. This makes it so that these types are serialized as lowercase text strings. But, also that lowercase strings can be deserialized to the items in this enum.

Implementing the simulated database

Now that we have the struct representing the data, we can create the simulated database object.

Let’s update main.rs. We’re including some structs and things from std and tokio, and our models module and create a new type:

use std::{collections::HashMap, convert::Infallible, sync::Arc};
use tokio::sync::Mutex;
use warp::Filter;

mod models;

type ItemsDb = Arc<Mutex<HashMap<usize, models::ShoppingListItem>>>;

#[tokio::main]
async fn main() {
    let items_db: ItemsDb = Arc::new(Mutex::new(HashMap::new()));
    let root = warp::path::end().map(|| "Welcome to my warp server!");
    let routes = root.with(warp::cors().allow_any_origin());

    warp::serve(routes).run(([127, 0, 0, 1], 5000)).await;
}

We are declaring the type for our simulated database on line 7. It is a HashMap with a integer as a key which will represent the “row id”. With Arc we create a reference counted object, and Mutex makes our data thread safe.

The ItemsDb type is a way of aliasing the Arc<Mutex<HashMap<usize, models::ShoppingListItem>>> declaration so that we can use it later for parameter types and the like.

Finally, we create an instance of this object to simulate a database on line 11.

Implementing the GET shoppings list items handler

With the data structure and database in place, we can build the GET request handler code for getting a list of shopping list items.

First, in main.rs we will create a new type for response object to be used with our handlers. And let’s already include the handlers module we will create. Changed lines are highlighted:

use std::{collections::HashMap, convert::Infallible, sync::Arc};
use tokio::sync::Mutex;
use warp::{Filter, Rejection};

mod handlers;
mod models;

type ItemsDb = Arc<Mutex<HashMap<usize, models::ShoppingListItem>>>;
type Result<T> = std::result::Result<T, Rejection>;

Now let’s create a new module file for handler code called handlers.rs. In this file, we will write the function for retrieving all data from the database get_shopping_list_items:

use crate::{models, ItemsDb, Result};
use warp::{http::StatusCode, reply, Reply};

pub async fn get_shopping_list_items(items_db: ItemsDb) -> Result<impl Reply> {
    let local_db = items_db.lock().await;
    let local_db: Vec<models::ShoppingListItem> = local_db.values().cloned().collect();

    Ok(reply::with_status(reply::json(&local_db), StatusCode::OK))
}

First, the function signature simply takes an instance of ItemsDb and returns a Result<impl Reply>.

Then, on line 5, we get the database instance by calling lock() on the mutex database object.

Then we take the values in the HashMap, clone them, and turn them into a Vec<models::ShoppingListItem> object called local_db. This is in order to send the shopping list items as a list in response to the client’s request.

Finally on the last few lines 8-11 we create the response object with reply::with_status. This function allows us to set the response body and the status. In this case, the body is the list of shopping list items and the status code is just OK, which is code 200.

Configuring the GET endpoint

In this last subsection, here we will tie everything together. We are going to configure the route that will allow clients to request data using a HTTP GET request. The response to the request will be a JSON array describing shopping list items. This is will be the first useful endpoint we build for our Rust REST API.

Here is the full code listing for main.rs with all the pieces. Below the code, we will go through the new additions and take a closer look at them. New and updated lines are highlighted:

use std::{collections::HashMap, convert::Infallible, sync::Arc};
use tokio::sync::Mutex;
use warp::{Filter, Rejection};

mod handlers;
mod models;

type ItemsDb = Arc<Mutex<HashMap<usize, models::ShoppingListItem>>>;
type Result<T> = std::result::Result<T, Rejection>;

#[tokio::main]
async fn main() {
    let items_db: ItemsDb = Arc::new(Mutex::new(HashMap::new()));
    let root = warp::path::end().map(|| "Welcome to my warp server!");
    let shopping_list_items_route = warp::path("shopping_list_items")
        .and(warp::get())
        .and(with_items_db(items_db.clone()))
        .and_then(handlers::get_shopping_list_items);
    let routes = root
        .or(shopping_list_items_route)
        .with(warp::cors().allow_any_origin());

    warp::serve(routes).run(([127, 0, 0, 1], 5000)).await;
}

fn with_items_db(
    items_db: ItemsDb,
) -> impl Filter<Extract = (ItemsDb,), Error = Infallible> + Clone {
    warp::any().map(move || items_db.clone())
}

First, let’s take a closer look at the new route configuration:

let shopping_list_items_route = warp::path("shopping_list_items")
        .and(warp::get())
        .and(with_items_db(items_db.clone()))
        .and_then(handlers::get_shopping_list_items);

Here we define the endpoint path using warp::path("shopping_list_items"). When the server runs we can reach it at the following address: http://localhost:5000/shopping_list_items.

On line 16, we indicate that this path handles GET requests.

Line 17 looks like it passes a “clone” of the database object but is actually a reference to the original source items_db. This is thanks to Arc, for more information see this page on Arc. With warp, we have to pass this object through the filter chain using a filter function.

This filter function called with_items_db can be found on lines 26-30:

fn with_items_db(
    items_db: ItemsDb,
) -> impl Filter<Extract = (ItemsDb,), Error = Infallible> + Clone {
    warp::any().map(move || items_db.clone())
}

This function returns a Filter that extracts the database object.

Finally, we indicate that this route should call the handlers::get_shopping_list_items we wrote earlier as a handler: .and_then(handlers::get_shopping_list_items);.

Using the endpoint to create shopping list items

When we run the warp server now, we can GET data from the /shopping_list_items endpoint. For example, using a curl command:

curl -X GET 'localhost:5000/shopping_list_items' -H 'Content-Type application/json'

[]

The last line is the return value we get from our Rust REST API server. Because we have no items in our database, our API returns an empty list. We can prefill the database, but let’s add an endpoint for adding items instead. So that we can add items to the database through our Rust REST API.

Implementing the create shopping list item functionality

In this section, we will build an endpoint and logic for creating shopping listy items in the database for our Rust REST API. Because we have done some groundwork in the previous section, adding a new endpoint is relatively easy.

We will be adding an endpoint for managing a single “shopping list item” resource: /shopping_list_item (so spelled singular, instead of plural). Eventually, we will configure GET, POST, PUT, and DELETE request handlers for this endpoint, but we start with POST.

First, let’s add a new handler function to handlers.rs for the POST request handler:

pub async fn create_shopping_list_item(
    mut shopping_list_item: models::ShoppingListItem,
    items_db: ItemsDb,
) -> Result<impl Reply> {
    println!("Received UserData: {:?}", shopping_list_item);
    let mut local_db = items_db.lock().await;
    let key_count = local_db.keys().len();
    shopping_list_item.item_id = Some(key_count);
    local_db.insert(key_count, shopping_list_item.clone());

    Ok(reply::with_status(
        reply::json(&shopping_list_item),
        StatusCode::CREATED,
    ))
}

The parameters for this function are the incoming shopping list item, shopping_list_item to insert into the database, and the database object itself: items_db.

We get the length of the array of keys (key_count) to function as an item id on line 17. Then on the following line, since item_id on the shopping_list_item object is an Option we have to put item_count in Some().

Now we can insert a clone of this new item with its id into our database on line 19, using key_count as the key. We have to insert a clone because we will be sending the item as a response at the end of the function.

Finally on the last few lines 21-24 we create the response object with reply::with_status. This function allows us to set the response body and the status. In this case, the body is the shopping_list_item with its newly assigned id and the CREATED status code, which is 201.

Configuring the POST endpoint for creating shopping list items

To finish this section, we are going to configure the route that will allow clients to send JSON describing a shopping list item in a POST request and have it be processed by the handler we just wrote.

First, let’s add the new path configuration:

let shopping_list_item_route = warp::path("shopping_list_item")
        .and(warp::post())
        .and(warp::body::json())
        .and(with_items_db(items_db.clone()))
        .and_then(handlers::create_shopping_list_item);

Here we define the path using warp::path(“shopping_list_item”). We will be able to reach it at the following address: http://localhost:5000/shopping_list_item.

  1. On line 21, we indicate that this path handles POST requests, so other types of requests will be ignored for now.
  2. The next line extracts JSON from the body of the request.
  3. Then we pass the database object to the filter chain. This is the same as with the get shopping list items route we configured earlier.
  4. Finally, we call the handler function handlers::create_shopping_list_item.

Using the endpoint to create shopping list items

If we run the warp server now we can POST data to the /shopping_list_items endpoint. Then an item will be created in the database. For example, using a curl command:

curl -X POST 'localhost:5000/shopping_list_items' -H "Content-Type: application/json" -d '{"name": "peanut butter", "item_type": "spread", "description": "made from peanuts", "price": 2.5}'

{"item_id":0,"name":"peanut butter","item_type":"spread","description":"made from peanuts","price":2.5}

The last line is the return value we get from our Rust REST API server.

Now when we send a GET request to localhost:5000/shopping_list_items we get a list with one item:

curl -X GET 'localhost:5000/shopping_list_items' -H 'Content-Type application/json'

[{"item_id":0,"name":"peanut butter","item_type":"spread","description":"made from peanuts","price":2.5}]

Implementing the “read” shopping list item functionality

Oftentimes with REST APIs, there is a need to retrieve a single specific item. In this section, we will build functionality to retrieve one shopping list item by id using a GET request for our Rust REST API.

First, we will write the handler function, and then the route configuration.

Implementing the GET shopping list item by id handler

Let’s add a new handler to handlers.rs called get_shopping_list_item_by_id:

pub async fn get_shopping_list_item_by_id(id: usize, items_db: ItemsDb) -> Result<impl Reply> {
    let local_db = items_db.lock().await;
    let shopping_list_item = match local_db.get(&id) {
        Some(item) => item,
        _ => {
            return Ok(reply::with_status(
                reply::json(&"{}"),
                StatusCode::NOT_FOUND,
            ));
        }
    };

    Ok(reply::with_status(
        reply::json(&shopping_list_item),
        StatusCode::OK,
    ))
}

This function has parameters for the id of the shopping list item that needs to be retrieved, and the database.

What is important here is the part where we attempt to get a shopping list item object by id on line 9. Because we can’t be sure that an entry exists for that id, we have to do error handling with match. When an item is found it is simply returned and assigned to shopping_list_item, otherwise we return a NOT_FOUND status code 404.

Finally, a reply with status is constructed with the shopping list item JSON as the body.

Configuring the get shopping list item by id endpoint

Again we have to configure a route that makes use of our handler function. So, let’s open main.rs and add code for that. We will add an or call to the shopping_list_item_route configuration we already had:

let shopping_list_item_route = warp::path("shopping_list_item")
        .and(warp::post())
        .and(warp::body::json())
        .and(with_items_db(items_db.clone()))
        .and_then(handlers::create_shopping_list_item)
        .or(warp::path!("shopping_list_item" / usize)
            .and(warp::get())
            .and(with_items_db(items_db.clone()))
            .and_then(handlers::get_shopping_list_item_by_id));

On line 25 we add or for the GET request route. Using path!() here allows us to easily define a path like /shopping_list_item/:id by passing "shopping_list_item" / usize. The rest are things we have already seen before.

Using the get shopping list item by id endpoint

We have finished implementing the third endpoint for our Rust REST API server. Let’s try it out with another curl command. We should add an item to the database first or else we will get a 404 Not found error:

curl -X POST 'localhost:5000/shopping_list_item' -H "Content-Type: application/json" -d '{"name": "peanut butter", "item_type": "spread", "description": "made from peanuts", "price": 2.5}'

{"item_id":0,"name":"peanut butter","item_type":"spread","description":"made from peanuts","price":2.5}

curl -X GET 'localhost:5000/shopping_list_item/0' -H 'Content-Type application/json'
{"item_id":0,"name":"peanut butter","item_type":"spread","description":"made from peanuts","price":2.5}

If we use an id number that does not exist we will get an empty body response and 404:

curl -v 'http://127.0.0.1:5000/shopping_list_item/99' -H 'Content-Type application/json'

...
< HTTP/1.1 404 Not Found
< content-type: application/json
< content-length: 4
< date: Mon, 25 Oct 2021 09:42:02 GMT
<
* Connection #0 to host 127.0.0.1 left intact
"{}"* Closing connection 0

Implementing the update shopping list item functionality

In this section, we will build functionality for updating an existing item for our Rust REST API. For example, if we want to change the description or price on an existing item we need a new endpoint for that. The endpoint address will be the same as the get shopping list by id endpoint. But, instead of handling GET requests it will handle PUT requests.

Adding a model for (partial) data updates

The nices way to do write an “update endpoint” is to allow partial updates. This means that the client only has to send the field(s) it wants to update, instead of having to send every field.

To make this work with deserialization, we need to add a new struct that can represent partial data. Let’s add a new model to models.rs:

#[derive(Debug, Deserialize, Clone)]
pub struct UpdateShoppingListItem {
    pub name: Option<String>,
    pub item_type: Option<ShoppingListItemType>,
    pub description: Option<String>,
    pub price: Option<f32>,
}

Every field is an Option now, and we don’t include item_id because the record id would normally not be updated anyway. We don’t need the Serialize attribute either, since we only have to deal with incoming data using this struct.

Implementing the update shopping list handler

It is time to add a new handler, let’s open up handlers.rs. This time we will be adding update_shopping_list_item_by_id. The function might look long, but it is mostly a repeat of the same pattern for doing partial field updates on the shopping list item:

pub async fn update_shopping_list_item_by_id(
    id: usize,
    updated_data: models::UpdateShoppingListItem,
    items_db: ItemsDb,
) -> Result<impl Reply> {
    let mut local_db = items_db.lock().await;
    let mut shopping_list_item = match local_db.get(&id) {
        Some(item) => item.clone(),
        _ => {
            return Ok(reply::with_status(
                reply::json(&"{}"),
                StatusCode::NOT_FOUND,
            ));
        }
    };

    match updated_data.name {
        Some(name) => {
            println!("updating name from {} to {}", shopping_list_item.name, name);
            shopping_list_item.name = name;
        }
        _ => {}
    };

    match updated_data.description {
        Some(description) => {
            println!(
                "updating description from {} to {}",
                shopping_list_item.description, description
            );
            shopping_list_item.description = description;
        }
        _ => {}
    };

    match updated_data.item_type {
        Some(item_type) => {
            println!(
                "updating item_type from {:?} to {:?}",
                shopping_list_item.item_type, item_type
            );
            shopping_list_item.item_type = item_type;
        }
        _ => {}
    };

    match updated_data.price {
        Some(price) => {
            println!(
                "updating price from {} to {}",
                shopping_list_item.price, price
            );
            shopping_list_item.price = price;
        }
        _ => {}
    };

    *local_db.get_mut(&id).unwrap() = shopping_list_item.clone();

    Ok(reply::with_status(
        reply::json(&shopping_list_item),
        StatusCode::OK,
    ))
}

Let’s go through it piece by piece. This first section is essentially the same as the first part of get_shopping_list_items_by_id:

pub async fn update_shopping_list_item_by_id(
    id: usize,
    updated_data: models::UpdateShoppingListItem,
    items_db: ItemsDb,
) -> Result<impl Reply> {
    let mut local_db = items_db.lock().await;
    let mut shopping_list_item = match local_db.get(&id) {
        Some(item) => item.clone(),
        _ => {
            return Ok(reply::with_status(
                reply::json(&"{}"),
                StatusCode::NOT_FOUND,
            ));
        }
    };

The only difference in the parameters is an extra parameter representing the updated fields. Then we look up the shopping list item using the id as the key. If not found, status code 404 (NOT_FOUND) will be returned.

The next part checks to see if a particular field on the updated_data object contains a value using match. If the field contains a value the associated shopping_list_item object’s field will get updated with that value.

 match updated_data.name {
        Some(name) => {
            println!("updating name from {} to {}", shopping_list_item.name, name);
            shopping_list_item.name = name;
        }
        _ => {}
    };

In this example, we match updated_data.name and if it contains a value: “Some(name)” then we print the previous value and the new value. Then we update the shopping list item object with the new value: shopping_list_item.name = name;. In any other case, we do nothing. The “_” is a placeholder that means “anything else” basically.

Configuring the update shopping list item by id endpoint

Finally, we have to configure the endpoint route so we can start updating items. This route looks similar to the POST route combined with the GET:

let shopping_list_item_route = warp::path("shopping_list_item")
        .and(warp::post())
        .and(warp::body::json())
        .and(with_items_db(items_db.clone()))
        .and_then(handlers::create_shopping_list_item)
        .or(warp::path!("shopping_list_item" / usize)
            .and(warp::get())
            .and(with_items_db(items_db.clone()))
            .and_then(handlers::get_shopping_list_item_by_id))
        .or(warp::path!("shopping_list_item" / usize)
            .and(warp::put())
            .and(warp::body::json())
            .and(with_items_db(items_db.clone()))
            .and_then(handlers::update_shopping_list_item_by_id));

Using the update shopping list item by id endpoint

Now we can update any existing item in the database by sending data with a PUT request to /shopping_list_item/:id:

curl -X POST 'localhost:5000/shopping_list_item' -H "Content-Type: application/json" -d '{"name": "peanut butter", "item_type": "spread", "description": "made from peanuts", "price": 2.5}'
{"item_id":0,"name":"peanut butter","item_type":"spread","description":"made from peanuts","price":2.5}

curl -X PUT 'localhost:5000/shopping_list_item/0' -H "Content-Type: application/json" -d '{"description": "made from real peanuts", "price": 4.5}'
{"item_id":0,"name":"peanut butter","item_type":"spread","description":"made from real peanuts","price":4.5}

curl -o - 'http://localhost:5000/shopping_list_items' -H 'Content-Type application/json'
[{"item_id":0,"name":"peanut butter","item_type":"spread","description":"made from real peanuts","price":4.5}]

Implementing the delete shopping list item by id functionality

In this section, we will build functionality for deleting a single shopping list item from the database by its id. This is the final step to build for our Rust REST API.

Implementing the delete shopping list item handler

We start by writing the handler code. This will be very simple in this case. So, let’s open the handlers.rs file and add the following code:

pub async fn delete_shopping_list_item_by_id(id: usize, items_db: ItemsDb) -> Result<impl Reply> {
    let mut local_db = items_db.lock().await;

    println!("deleting shopping list item with id: {}", id);
    local_db.remove(&id);

    Ok(reply::with_status(
        reply::html("delete success"),
        StatusCode::OK,
    ))
}

This function’s signature is the same as that of get_shopping_list_item_by_id. While in the contents we simply print the id of the item that is about to be deleted and then call remove() on the database object.

We should note that we are not checking if the item exists or not. Because it doesn’t really matter in either case the item with that id won’t exist in the database.

Configuring the delete shopping list item by id endpoint

The endpoint route configuration is the same as that for “get by id”, but with a different HTTP request type:

let shopping_list_item_route = warp::path("shopping_list_item")
        .and(warp::post())
        .and(warp::body::json())
        .and(with_items_db(items_db.clone()))
        .and_then(handlers::create_shopping_list_item)
        .or(warp::path!("shopping_list_item" / usize)
            .and(warp::get())
            .and(with_items_db(items_db.clone()))
            .and_then(handlers::get_shopping_list_item_by_id))
        .or(warp::path!("shopping_list_item" / usize)
            .and(warp::put())
            .and(warp::body::json())
            .and(with_items_db(items_db.clone()))
            .and_then(handlers::update_shopping_list_item_by_id))
        .or(warp::path!("shopping_list_item" / usize)
            .and(warp::delete())
            .and(with_items_db(items_db.clone()))
            .and_then(handlers::delete_shopping_list_item_by_id));

This pattern should be very familiar to us now.

Using the delete shopping list item by id endpoint

With this new endpoint, we can remove items from the database using a DELETE HTTP request to the following endpoint: /shopping_list_item/:id.

Let’s try it out by adding an item to the database, checking it is there, then deleting it, and checking again. See the example commands below:

curl -X POST 'localhost:5000/shopping_list_item' -H "Content-Type: application/json" -d '{"name": "peanut butter", "item_type": "spread", "description": "made from peanuts", "price": 2.5}'
{"item_id":0,"name":"peanut butter","item_type":"spread","description":"made from peanuts","price":2.5}

curl -o - 'http://localhost:5000/shopping_list_items' -H 'Content-Type application/json'
[{"item_id":0,"name":"peanut butter","item_type":"spread","description":"made from peanuts","price":2.5}]

curl -X DELETE 'localhost:5000/shopping_list_item/0'
delete success

curl -o - 'http://localhost:5000/shopping_list_items' -H 'Content-Type application/json'
[]

Conclusion

With all of this in place, we now know how to build a basic Rust REST API using warp. We have covered all the basic operations Create, Read, Update, and delete. Using the proper HTTP Request types: POST, GET, PUT, and DELETE. We now have a good knowledge basis to build more complex REST APIs on.

The full code example for this tutorial can be found on my GitHub: here.

I also have a tutorial on how to make our REST API more secure with JSON Web Tokens:

Please follow me on Twitter for updates on upcoming Rust programming articles: JWT security for a Rust REST API: how to.

Comments (1)

Leave a Reply

Your email address will not be published. Required fields are marked *