Actix-webとは?
Actix-webは、Rustで書かれた高性能なWebフレームワークです。TechEmpower Framework Benchmarksでも上位に位置する非常に高速なフレームワークで、本番環境での利用実績も豊富です。
この記事では、Actix-webを使って基本的なREST APIを構築します。インメモリのデータ(Todoリスト)を題材にして、**CRUD(Create / Read / Update / Delete)**の4つの操作を実装していきます。
プロジェクトのセットアップ
まず新しいCargoプロジェクトを作成します。
cargo new actix-todo-api
cd actix-todo-api
Cargo.toml に必要な依存関係を追加します。
[dependencies]
actix-web = "4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
uuid = { version = "1", features = ["v4"] }
serde/serde_json:JSONのシリアライズ・デシリアライズuuid:ユニークなIDの生成
データ構造の定義
src/main.rs にTodoアイテムの構造体を定義します。
use actix_web::{web, App, HttpServer, HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Todo {
id: String,
title: String,
done: bool,
}
#[derive(Deserialize)]
struct CreateTodo {
title: String,
}
#[derive(Deserialize)]
struct UpdateTodo {
title: Option<String>,
done: Option<bool>,
}
// アプリケーション全体で共有する状態
struct AppState {
todos: Mutex<Vec<Todo>>,
}
Mutex<Vec<Todo>> を使って、複数のリクエストが同時にデータにアクセスしても安全なようにスレッドセーフな共有状態を定義しています。
CRUDハンドラの実装
GET /todos ─ 一覧取得
async fn get_todos(data: web::Data<AppState>) -> impl Responder {
let todos = data.todos.lock().unwrap();
HttpResponse::Ok().json(todos.clone())
}
POST /todos ─ 新規作成
async fn create_todo(
data: web::Data<AppState>,
body: web::Json<CreateTodo>,
) -> impl Responder {
let mut todos = data.todos.lock().unwrap();
let new_todo = Todo {
id: Uuid::new_v4().to_string(),
title: body.title.clone(),
done: false,
};
todos.push(new_todo.clone());
HttpResponse::Created().json(new_todo)
}
web::Json<CreateTodo> でリクエストボディを自動的にデシリアライズしてくれます。
PUT /todos/ ─ 更新
async fn update_todo(
data: web::Data<AppState>,
path: web::Path<String>,
body: web::Json<UpdateTodo>,
) -> impl Responder {
let id = path.into_inner();
let mut todos = data.todos.lock().unwrap();
if let Some(todo) = todos.iter_mut().find(|t| t.id == id) {
if let Some(title) = &body.title {
todo.title = title.clone();
}
if let Some(done) = body.done {
todo.done = done;
}
HttpResponse::Ok().json(todo.clone())
} else {
HttpResponse::NotFound().body("Todo not found")
}
}
web::Path<String> でURLパスのパラメータを受け取ります。find でIDが一致するTodoを探し、存在すれば更新・なければ404を返します。
DELETE /todos/ ─ 削除
async fn delete_todo(
data: web::Data<AppState>,
path: web::Path<String>,
) -> impl Responder {
let id = path.into_inner();
let mut todos = data.todos.lock().unwrap();
let before_len = todos.len();
todos.retain(|t| t.id != id);
if todos.len() < before_len {
HttpResponse::NoContent().finish()
} else {
HttpResponse::NotFound().body("Todo not found")
}
}
retain を使って、対象のIDを持つ要素を取り除きます。
ルーティングとサーバーの起動
最後に main 関数でルーティングを設定し、サーバーを起動します。
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let state = web::Data::new(AppState {
todos: Mutex::new(vec![]),
});
HttpServer::new(move || {
App::new()
.app_data(state.clone())
.route("/todos", web::get().to(get_todos))
.route("/todos", web::post().to(create_todo))
.route("/todos/{id}", web::put().to(update_todo))
.route("/todos/{id}", web::delete().to(delete_todo))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
動作確認
サーバーを起動します。
cargo run
curlでAPIを試してみましょう。
# Todo作成
curl -X POST http://localhost:8080/todos \
-H "Content-Type: application/json" \
-d '{"title": "Rustを学ぶ"}'
# 一覧取得
curl http://localhost:8080/todos
# 更新({id}は実際のIDに置き換え)
curl -X PUT http://localhost:8080/todos/{id} \
-H "Content-Type: application/json" \
-d '{"done": true}'
# 削除
curl -X DELETE http://localhost:8080/todos/{id}
まとめ
この記事では、Actix-webを使ってREST APIのCRUDエンドポイントを実装しました。
| 操作 | メソッド | パス |
|---|---|---|
| 一覧取得 | GET | /todos |
| 新規作成 | POST | /todos |
| 更新 | PUT | /todos/ |
| 削除 | DELETE | /todos/ |
Actix-webは非同期処理を活かした高速なフレームワークです。web::Data による状態共有、web::Json によるボディのデシリアライズなど、実用的な機能が揃っています。次のステップとして、**データベース(PostgreSQL + SQLx)**との連携に挑戦してみてください!