Prerequisites

Before you begin, ensure you have the following installed:

  • Rust (with Cargo)
  • A code editor (like Visual Studio Code)
  • Postman or a similar tool for testing API endpoints

Setting Up Your Rust Project

To start, create a new Rust project using Cargo:

cargo new rust_api
cd rust_api

Next, add the Actix-web dependency to your Cargo.toml file:

[dependencies]
actix-web = "4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Now, let’s create a simple data model for our API. Create a new file called models.rs in the src directory:

// src/models.rs
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone)]
pub struct Item {
    pub id: usize,
    pub name: String,
    pub description: String,
}

Implementing the API

Now, let’s implement the API in main.rs. Open src/main.rs and add the following code:

// src/main.rs
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde_json::json;
use std::sync::Mutex;

mod models;
use models::Item;

struct AppState {
    items: Mutex<Vec<Item>>,
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let app_state = web::Data::new(AppState {
        items: Mutex::new(vec![]),
    });

    HttpServer::new(move || {
        App::new()
            .app_data(app_state.clone())
            .route("/items", web::post().to(create_item))
            .route("/items", web::get().to(get_items))
            .route("/items/{id}", web::get().to(get_item))
            .route("/items/{id}", web::put().to(update_item))
            .route("/items/{id}", web::delete().to(delete_item))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

async fn create_item(item: web::Json<Item>, data: web::Data<AppState>) -> impl Responder {
    let mut items = data.items.lock().unwrap();
    items.push(item.into_inner());
    HttpResponse::Created().json(item.into_inner())
}

async fn get_items(data: web::Data<AppState>) -> impl Responder {
    let items = data.items.lock().unwrap();
    HttpResponse::Ok().json(&*items)
}

async fn get_item(web::Path(id): web::Path<usize>, data: web::Data<AppState>) -> impl Responder {
    let items = data.items.lock().unwrap();
    if let Some(item) = items.iter().find(|item| item.id == id) {
        HttpResponse::Ok().json(item)
    } else {
        HttpResponse::NotFound().finish()
    }
}

async fn update_item(web::Path(id): web::Path<usize>, item: web::Json<Item>, data: web::Data<AppState>) -> impl Responder {
    let mut items = data.items.lock().unwrap();
    if let Some(existing_item) = items.iter_mut().find(|item| item.id == id) {
        existing_item.name = item.name.clone();
        existing_item.description = item.description.clone();
        HttpResponse::Ok().json(existing_item)
    } else {
        HttpResponse::NotFound().finish()
    }
}

async fn delete_item(web::Path(id): web::Path<usize>, data: web::Data<AppState>) -> impl Responder {
    let mut items = data.items.lock().unwrap();
    if items.iter().any(|item| item.id == id) {
        items.retain(|item| item.id != id);
        HttpResponse::NoContent().finish()
    } else {
        HttpResponse::NotFound().finish()
    }
}

Explanation of the Code

  1. Data Model: The Item struct represents the data model for our API. It uses Serde for serialization and deserialization.
  1. AppState: This struct holds a thread-safe vector of items using a Mutex. This allows for safe concurrent access to our data.
  1. HTTP Server: The HttpServer is set up to listen on 127.0.0.1:8080. We define routes for creating, reading, updating, and deleting items.
  1. CRUD Functions:
  • create_item: Adds a new item to the collection.
  • get_items: Returns all items.
  • get_item: Returns a specific item by ID.
  • update_item: Updates an existing item based on its ID.
  • delete_item: Removes an item from the collection.

Testing the API

You can test the API using Postman or curl. Here are some example commands:

  1. Create an Item:
curl -X POST http://127.0.0.1:8080/items -H "Content-Type: application/json" -d '{"id": 1, "name": "Item 1", "description": "This is item 1."}'
  1. Get All Items:
curl http://127.0.0.1:8080/items
  1. Get a Specific Item:
curl http://127.0.0.1:8080/items/1
  1. Update an Item:
curl -X PUT http://127.0.0.1:8080/items/1 -H "Content-Type: application/json" -d '{"id": 1, "name": "Updated Item 1", "description": "This is the updated item."}'
  1. Delete an Item:
curl -X DELETE http://127.0.0.1:8080/items/1

Conclusion

In this tutorial, you have learned how to create a simple RESTful API using Rust and Actix-web. You have implemented basic CRUD operations and tested your API with curl. This foundational knowledge will enable you to build more complex applications in Rust.

Learn more with useful resources