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.
Contents
- 1 Prerequisites
- 2 What is a REST API
- 3 Project set up and dependencies
- 4 Basic warp server set up
- 5 Implementing a “read” shopping list items functionality
- 6 Implementing the create shopping list item functionality
- 7 Implementing the “read” shopping list item functionality
- 8 Implementing the update shopping list item functionality
- 9 Implementing the delete shopping list item by id functionality
- 10 Conclusion
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:
- Create project.
- Implement barebones Warp server.
- Add HashMap to simulate a database.
- 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 aShoppingListItem
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 aGET
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::
we wrote earlier as a handler: get_shopping_list_items
.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
.
- On line 21, we indicate that this path handles
POST
requests, so other types of requests will be ignored for now. - The next line extracts JSON from the body of the request.
- 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.
- 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.
Follow @tmdev82
Thanks,
You forgot to add each new route to routes in main.