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 been actively used for building high-load web services and REST APIs. In this guide, we will create a full-fledged REST API in Rust using the Actix-web framework in 30 minutes.
Why choose Rust for REST API?
- Performance: Rust compiles to native code and does not use a garbage collector, providing maximum request processing speed.
- Safety: The ownership and borrowing system eliminates entire classes of memory-related errors.
- Scalability: Actix-web is one of the fastest web frameworks in the world, capable of handling millions of requests per second.
By the end of this article, you will have a ready-made project with several endpoints, error handling, and a structure for further expansion.
Step 1: Project Setup and Dependency Installation
First, let's create a new Cargo project. Open your terminal and run:
cargo new rust_rest_apicd rust_rest_apiNow open the Cargo.toml file and add the following dependencies:
[dependencies]actix-web = "4"serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"tokio = { version = "1", features = ["full"] }uuid = { version = "1.0", features = ["v4", "serde"] }Let's break down what each library does:
- actix-web — the main web framework for creating an HTTP server.
- serde / serde_json — serialization and deserialization of data into JSON format.
- tokio — an asynchronous runtime required for Actix to work.
- uuid — generation of unique identifiers for our entities.
Run cargo build to download all dependencies. This will take some time on the first run.
Step 2: Creating the Data Model and Storage
Our API will manage a list of tasks (a TODO application). Let's create a simple model. Open src/main.rs and add the following code:
use actix_web::{web, App, HttpServer, HttpResponse, middleware::Logger};use serde::{Deserialize, Serialize};use std::sync::Mutex;use uuid::Uuid;
// Task model#[derive(Debug, Serialize, Deserialize, Clone)]struct Task { id: Uuid, title: String, completed: bool,}
// Request to create a task#[derive(Debug, Deserialize)]struct CreateTaskRequest { title: String,}
// Task storage (in memory)struct AppState { tasks: Mutex>,} We use Mutex for thread-safe data access. In a real project, you would replace this with a database, but this option works for demonstration purposes.
Now let's add a function to initialize the application state:
fn init_tasks() -> Vec { vec![ Task { id: Uuid::new_v4(), title: "Learn Rust".to_string(), completed: false, }, Task { id: Uuid::new_v4(), title: "Write REST API".to_string(), completed: true, }, ]} Step 3: Implementing CRUD Endpoints
Now let's create the main handlers for our REST API. Add the following code after the model declarations:
GET /tasks — get a list of all tasks
async fn get_tasks(data: web::Data<AppState>) -> HttpResponse { let tasks = data.tasks.lock().unwrap(); HttpResponse::Ok().json(tasks.clone())}POST /tasks — create a new task
async fn create_task( data: web::Dat