Introduction: Why Rust for REST API?
Rust is a modern systems programming language that combines the performance of C/C++ with memory safety. In recent years, it has been actively used for building high-load web services and REST APIs. Thanks to the Actix-web framework, you can write a full-fledged REST API in Rust in just 30 minutes, even if you're just starting to learn the language.
In this article, we will create a simple but functional REST API for managing a Todo List. You will learn:
- How to set up a Rust project with Actix-web
- How to implement CRUD operations (Create, Read, Update, Delete)
- How to work with JSON requests and responses
- How to use routing and handlers
Ready? Let's go!
Step 1: Project Setup and Dependency Installation
First, make sure you have Rust installed. If not, run the command:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shCreate a new project:
cargo new rust_todo_apicd rust_todo_apiOpen Cargo.toml and add the following dependencies:
[dependencies]actix-web = "4"serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"tokio = { version = "1", features = ["full"] }Here:
- actix-web — a powerful asynchronous web framework
- serde — for JSON serialization/deserialization
- tokio — an asynchronous runtime (Actix-web uses it under the hood)
Run cargo build to download and compile the dependencies. This will take 2-3 minutes.
Step 2: Creating the Data Model and Storage
In this project, we won't connect a database — for simplicity, we'll use an in-memory vector. Let's create a structure for the task.
Open src/main.rs and write the following code:
use actix_web::{web, App, HttpServer, HttpResponse, middleware};use serde::{Deserialize, Serialize};use std::sync::Mutex;
// Task model#[derive(Serialize, Deserialize, Clone)]struct Task { id: u32, title: String, completed: bool,}
// Task storage (thread-safe)struct AppState { tasks: Mutex<Vec<Task>>,}Explanation:
- #[derive(Serialize, Deserialize)] — automatically generates methods for working with JSON
- Mutex — ensures safe access to data from multiple threads
- AppState — will be stored in the application state
Step 3: Implementing CRUD Handlers
Now let's add handler functions for each HTTP method. Insert the following code after the struct declarations:
// Get all tasksasync fn get_tasks(data: web::Data<AppState>) -> HttpResponse { let tasks = data.tasks.lock().unwrap(); HttpResponse::Ok().json(tasks.clone())}
// Create a new taskasync fn create_task(data: web::Data<AppState>, body: web::Json<Task>) -> HttpResponse { let mut tasks = data.tasks.lock().unwrap(); let new_id = tasks.len() as u32 + 1; let task = Task { id: new_id, title: body.title.clone(), completed: body.completed, }; tasks.push(task.clone()); HttpResponse::Created().json(task)}
// Update a taskasync fn update_task( data: web::Data<AppState>, path: web::Path<u32>, body: web::Json<Task>,) -> HttpResponse { let mut tasks = data.tasks.lock().unwrap(); let id = path.into_inner(); if let Some(task) = tasks.iter_mut().find(|t| t.id == id) { task.title = body.title.clone(); task.completed = body.completed;