🐷

Actix Web に入門して REST API サーバーを作る

2024/07/16に公開

はじめに

今回は Rust の Web フレームワークとしてかなり有名である、Actix-Webのチュートリアル部分をやっていこうと思います。

Rust には Web フレームワークが多く存在しますが、その中でも比較的歴史があり、第一線で活躍するフレームワークの一つが Actix-Webです。
チュートリアル的な内容ですので、これから Actix-Webを触る方の助けになればと思います。

他のWebフレームワークとの比較は以下の記事が参考になりました。
https://synamon.hatenablog.com/entry/rust-server-framework-comparison

🔻Actix Web
https://actix.rs/

プロジェクト作成

事前に Rust が入っていることが前提で進めていきます。

以下のコマンドで、 hello-worldプロジェクトを作成する。

cargo new hello-world
cd hello-world

以下のコマンドを実行し、actix-webのクレートを追加します。

cargo add actix-web

Cargo.tomlが以下のように更新されていることを確認します。

Cargo.toml
[package]
name = "hello-world"
version = "0.1.0"
edition = "2021"

[dependencies]
actix-web = "4.8.0"

※2024/07/15時点で最新バージョンが 4.8.0でした。一旦現時点での最新バージョンでやっていきます。

念のため、私は cargo runを実行しておきました。

cargo run
Hello, world!

REST API サーバーを構築する

メインの実装

main.rsを以下のように編集します。

main.rs
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};

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

#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
    HttpResponse::Ok().body(req_body)
}

async fn manual_hello() -> impl Responder {
    HttpResponse::Ok().body("Hey there!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(hello)
            .service(echo)
            .route("/hey", web::get().to(manual_hello))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

上記のコードでは、3つのエンドポイントが実装されているはずです。

  • GET /
  • POST /echo
  • GET /hey

それぞれのエンドポイントにアクセスして、どのような処理が行われているのかをざっくりとみていければと思います。
これで、 cargo runを実行します。

動作の確認

GET /

任意のブラウザで、http://127.0.0.1:8080/にアクセスすると、Hello world!が出力されていることを確認できました。

Hello world!

main関数の中で以下の部分が呼び出されて、Hello world!が出力される仕組みですね。
関数の前に、メソッドとパスがそのまま記述されていることがわかります。

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

GET /hey

こちらも、任意のブラウザでhttp://127.0.0.1:8080/heyにアクセスすると、Hey there!と出力されることを確認することができました。

Hey there!

これは、main関数の中にメソッドとパスが記述されているタイプで、呼び出し先の関数のみを定義する方法のようです。
ルーティングの方法に2つの実装方法があるようですね!

async fn manual_hello() -> impl Responder {
    HttpResponse::Ok().body("Hey there!")
}

POST /echo

POST エンドポイントですので、任意のクライアント実行ツールを使い、
http://127.0.0.1:8080/echoにアクセスします。(私は Thunder Clientという VScode の拡張機能を使用します。)

Bodyには任意の JSONを入れることで、そのまま応答として返されます。

こちらも、Hello, world!出力のパターンと同様に、echo関数の前にメソッドやパスを定義していることがわかります。

#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
    HttpResponse::Ok().body(req_body)
}

追加の実装をしてみる

チュートリアルのコピペでは楽しくないので、
簡単ですが、ヘルスチェックのエンドポイントを実装していきます!

GET /healthにアクセスすると以下のような JSONが帰ってくることとして実装します。

{
  "error": false, // エラーかどうか
  "date_time": "2024-07-15 18:57:13" // 現在の日時
}

以下のコマンドで、serdechronoを追加します。

cargo add serde --features derive
cargo add chrono

main.rsに以下のコードを追加してみます。

main.rs
use chrono::Local;
use serde::Serialize;

#[derive(Serialize)]
struct HealthResponse {
    error: bool,
    date_time: String,
}

#[get("/")]
async fn health() -> impl Responder {
    let response = HealthResponse {
        error: false,
        date_time: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
    };
    web::Json(response)
}

さらに、ルーティングの部分に以下を追加。

main.rs
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(hello)
            .service(echo)
            // 新しく追加
            .service(health)
            .route("/hey", web::get().to(manual_hello))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

ここまでできたら、再度 cargo runを実行し確認してみます。
ブラウザか、APIクライアントツールで http://127.0.0.1:8080/healthにアクセスします。

以下のように、応答が返ってくることが確認できました!
health

おわりに

いかがでしたでしょうか。
チュートリアルと、追加のエンドポイントの実装を行ってみました。

贔屓目なしでもとても簡単に REST API サーバーが構築できると感じました。
ルーティング部分が非常に簡易的だと感じました。
.route().service()のどちらでルーティングする方が多いのでしょうね。
ここはプロジェクト文化によって変わるところかもしれませんが、私は後者の方が良いかなと思いました。

また Actix-Webのあれこれを記事化できればと思います!

では。

コラボスタイル Developers

Discussion