Rust actix-web入門:Hello Worldからルーティングまで

スポンサーリンク

Rust actix-web入門:Hello Worldからルーティングまで

はじめに

actix-webはRustで最もよく使われるWebフレームワークの1つです。高いパフォーマンスと豊富な機能を持ち、APIサーバーやWebアプリケーションの開発に使われています。

この記事では actix-web 4.x を使って、プロジェクト作成からルーティング・パラメータ取得・JSONレスポンスまでを解説します。


プロジェクトの作成

新しいRustプロジェクトを作成します。

cargo new actix-hello
cd actix-hello

Cargo.toml に依存関係を追加します。

[dependencies]
actix-web = "4"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

serde はJSON変換のために使います。


Hello Worldを動かす

src/main.rs を以下のように書き換えます。

use actix_web::{get, App, HttpResponse, HttpServer, Responder};

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello, World!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(hello)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

サーバーを起動します。

cargo run

別のターミナルで動作を確認します。

curl http://127.0.0.1:8080/
# Hello, World!

ルーティング

複数のパスとHTTPメソッドを登録できます。

use actix_web::{delete, get, post, put, App, HttpResponse, HttpServer, Responder};

#[get("/users")]
async fn get_users() -> impl Responder {
    HttpResponse::Ok().body("ユーザー一覧")
}

#[post("/users")]
async fn create_user() -> impl Responder {
    HttpResponse::Created().body("ユーザー作成")
}

#[put("/users/{id}")]
async fn update_user() -> impl Responder {
    HttpResponse::Ok().body("ユーザー更新")
}

#[delete("/users/{id}")]
async fn delete_user() -> impl Responder {
    HttpResponse::Ok().body("ユーザー削除")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(get_users)
            .service(create_user)
            .service(update_user)
            .service(delete_user)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

パスパラメータを取得する

URLの一部を変数として受け取るには web::Path を使います。

use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};

#[get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> impl Responder {
    let id = path.into_inner();
    HttpResponse::Ok().body(format!("ユーザーID: {}", id))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new().service(get_user)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}
curl http://127.0.0.1:8080/users/42
# ユーザーID: 42

複数のパスパラメータを受け取る場合はタプルを使います。

#[get("/users/{user_id}/posts/{post_id}")]
async fn get_post(path: web::Path<(u32, u32)>) -> impl Responder {
    let (user_id, post_id) = path.into_inner();
    HttpResponse::Ok().body(format!("ユーザー{}の投稿{}", user_id, post_id))
}

クエリパラメータを取得する

?key=value 形式のクエリパラメータは web::Query で受け取ります。

use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;

#[derive(Deserialize)]
struct SearchQuery {
    q: String,
    limit: Option<u32>,
}

#[get("/search")]
async fn search(query: web::Query<SearchQuery>) -> impl Responder {
    let limit = query.limit.unwrap_or(10);
    HttpResponse::Ok().body(format!("検索: {} (最大{}件)", query.q, limit))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new().service(search)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}
curl "http://127.0.0.1:8080/search?q=rust&limit=5"
# 検索: rust (最大5件)

JSONを返す

serde を使って構造体をJSONとして返せます。

use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use serde::Serialize;

#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

#[get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> impl Responder {
    let id = path.into_inner();
    let user = User {
        id,
        name: "田中太郎".to_string(),
        email: "tanaka@example.com".to_string(),
    };
    HttpResponse::Ok().json(user)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new().service(get_user)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}
curl http://127.0.0.1:8080/users/1
# {"id":1,"name":"田中太郎","email":"tanaka@example.com"}

JSONボディを受け取る

POSTリクエストのJSONボディを受け取るには web::Json を使います。

use actix_web::{post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct CreateUserRequest {
    name: String,
    email: String,
}

#[post("/users")]
async fn create_user(body: web::Json<CreateUserRequest>) -> impl Responder {
    println!("新規ユーザー: {} ({})", body.name, body.email);
    HttpResponse::Created().json(&*body)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new().service(create_user)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}
curl -X POST http://127.0.0.1:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name":"山田花子","email":"yamada@example.com"}'
# {"name":"山田花子","email":"yamada@example.com"}

まとめ

機能 書き方
GETルート #[get("/path")] async fn handler() -> impl Responder
パスパラメータ path: web::Path<T>
クエリパラメータ query: web::Query<T>Deserializeが必要)
JSONレスポンス HttpResponse::Ok().json(value)
JSONボディ受信 body: web::Json<T>Deserializeが必要)
サーバー起動 HttpServer::new(|| App::new().service(...)).bind(...).run()

actix-webはRustのトレイトやライフタイムの知識があると理解しやすくなります。トレイトの基本は「Rustのトレイト入門:実装方法を基礎から解説」を参照してください。