In-Memory JSON CRUD API
Technical Review Approved
Updated: 2026-09-13
Actix Web 4.x
RS Rust 1.75+
VERIFIED
A complete REST API managing a To-Do list using Shared Application State (Mutex), JSON extractors, and Path parameters.
Actix Web: 4.x
Rust: 1.75+
What You Will Build
A JSON REST API that allows creating, listing, and retrieving tasks stored safely in an in-memory thread-safe Mutex.
What You Will Learn
- How to parse JSON bodies (`web::Json`)
- How to extract Path parameters (`web::Path`)
- How to share mutable state across worker threads (`web::Data<Mutex<T>>`)
Project Setup
src/main.rs
Edition 2024
1cargo new json-api2cd json-api
Dependencies (`Cargo.toml`)
src/main.rs
Edition 2024
1[dependencies]2actix-web = "4"3serde = { version = "1.0", features = ["derive"] }
Complete Code
src/main.rs
Click highlighted lines to inspect mechanics1use actix_web::{web, App, HttpServer, HttpResponse, Responder};
2use serde::{Deserialize, Serialize};
3use std::sync::Mutex;
4
5#[derive(Serialize, Clone)]
6struct Task {
7 id: u32,
8 description: string,
9}
10
11#[derive(Deserialize)]
12struct CreateTask {
13 description: String,
14}
15
16// Our shared application state
17struct AppState {
18 tasks: Mutex<Vec<Task>>,
19}
20
21// 1. GET /tasks
22async fn get_tasks(data: web::Data<AppState>) -> impl Responder {
23 let tasks = data.tasks.lock().unwrap();
24 HttpResponse::Ok().json(&*tasks)
25}
26
27// 2. POST /tasks
28async fn create_task(req_body: web::Json<CreateTask>, data: web::Data<AppState>) -> impl Responder {
29 let mut tasks = data.tasks.lock().unwrap();
30 let new_id = (tasks.len() as u32) + 1;
31 let new_task = Task {
32 id: new_id,
33 description: req_body.description.clone(),
34 };
35 tasks.push(new_task.clone());
36 HttpResponse::Created().json(new_task)
37}
38
39// 3. GET /tasks/{id}
40async fn get_task_by_id(path: web::Path<u32>, data: web::Data<AppState>) -> impl Responder {
41 let target_id = path.into_inner();
42 let tasks = data.tasks.lock().unwrap();
43
44 if let Some(task) = tasks.iter().find(|t| t.id == target_id) {
45 HttpResponse::Ok().json(task)
46 } else {
47 HttpResponse::NotFound().body("Task not found")
48 }
49}
50
51#[actix_web::main]
52async fn main() -> std::io::Result<()> {
53 // Initialize state OUTSIDE the closure so it is shared across all threads
54 let state = web::Data::new(AppState {
55 tasks: Mutex::new(Vec::new()),
56 });
57
58 println!("Server starting on http://127.0.0.1:8080");
59
60 HttpServer::new(move || {
61 App::new()
62 .app_data(state.clone())
63 .route("/tasks", web::get().to(get_tasks))
64 .route("/tasks", web::post().to(create_task))
65 .route("/tasks/{id}", web::get().to(get_task_by_id))
66 })
67 .bind(("127.0.0.1", 8080))?
68 .run()
69 .await
70}
Line 22 - 23
Expected Result
src/main.rs
Edition 2024
1$ curl -X POST http://127.0.0.1:8080/tasks -H "Content-Type: application/json" -d '{"description": "Learn Actix Web"}'2{"id":1,"description":"Learn Actix Web"}34$ curl http://127.0.0.1:8080/tasks/15{"id":1,"description":"Learn Actix Web"}
How It Works
The application registers a shared `web::Data` object containing a `Mutex<Vec<Task>>`. Whenever a request hits the `/tasks` endpoints, the handler extracts the `Data`, locks the mutex safely, and either reads or appends to the vector. `web::Json` automatically parses the incoming POST body, and `HttpResponse::Ok().json()` automatically serializes the response back to the client.