Introduction: Why Rust for REST API?
Rust is a modern systems programming language that combines the performance of C++ with memory safety. In recent years, it has become a popular choice for building high-load web services. If you are already familiar with Python and want to try something new, Rust will be an excellent step forward.
In this article, we will create a full-fledged REST API for managing a task list (Todo List) in Rust using the Actix-web framework. The entire process will take no more than 30 minutes if you follow the instructions.
1. Environment Setup
Installing Rust
If you don't have Rust installed yet, run the following command in the terminal (Linux/macOS):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shFor Windows, download the installer from the official website.
After installation, check the version:
rustc --versionCreating a Project
Let's create a new project using Cargo, the Rust package manager:
cargo new rust_todo_apicd rust_todo_apiAdding Dependencies
Open the Cargo.toml file and add the following dependencies:
[dependencies]actix-web = "4"actix-rt = "2"serde = { version = "1", features = ["derive"] }serde_json = "1"tokio = { version = "1", features = ["full"] }Save the file. Cargo will automatically download all libraries on the first build.
2. Creating the Data Model and Storage
Before writing endpoints, let's define the data structure. We will use a simple in-memory vector (for demonstration purposes).
Task Model
Create the file src/models.rs:
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]pub struct Task { pub id: u32, pub title: String, pub completed: bool,}Global Storage
In src/main.rs, we will add global state. For thread safety, we use Mutex:
use std::sync::Mutex;use actix_web::{web, App, HttpServer, HttpResponse, HttpRequest, Responder};use models::Task;
mod models;
struct AppState { tasks: Mutex<Vec<Task>>, next_id: Mutex<u32>,}3. Implementing CRUD Endpoints
Now let's write the main handlers. They will all receive the application state via web::Data.
GET /tasks — Get All Tasks
async fn get_tasks(data: web::Data<AppState>) -> impl Responder { let tasks = data.tasks.lock().unwrap(); HttpResponse::Ok().json(tasks.clone())}POST /tasks — Create a New Task
async fn create_task(data: web::Data<AppState>, body: web::Json<Task>) -> impl Responder { let mut tasks = data.tasks.lock().unwrap(); let mut next_id = data.next_id.lock().unwrap(); let new_task = Task { id: *next_id, title: body.title.clone(), completed: false, }; tasks.push(new_task.clone()); *next_id += 1; HttpResponse::Created().json(new_task)}GET /tasks/{id} — Get Task by ID
async fn get_task(data: web::Data<AppState>, path: web::Path<u32>) -> impl Responder { let id = path.into_inner(); let tasks = data.tasks.lock().unwrap(); match tasks.iter().find(|t| t.id == id) { Some(task) => HttpResponse::Ok().json(task), None => HttpResponse::NotFound().json("Task not found"), }}DELETE /tasks/{id} — Delete a Task
async fn delete_task(data: web::Data<AppState>, path: web::Path<u32>) -> impl Responder { let id = path.into_inner(); l