Open19

Rust で axum を使って REST API を作ってみる

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

依存関係の追加

Cargo.toml
[package]
name = "first_rest_api"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = "0.7.5"
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

コーディング

src/main.rs
use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();

    axum::serve(listener, app).await.unwrap();
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ルーティング

src/main.rs
use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(root))
        .route("/foo", get(get_foo).post(post_foo))
        .route("/foo/bar", get(foo_bar));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn root() -> String {
    String::from("root\n")
}

async fn get_foo() -> String {
    String::from("get_foo\n")
}

async fn post_foo() -> String {
    String::from("post_foo\n")
}

async fn foo_bar() -> String {
    String::from("foo_bar\n")
}
コマンド
curl http://localhost:3000/
curl http://localhost:3000/foo
curl -X POST http://localhost:3000/foo
curl http://localhost:3000/foo/bar
curl -v http://localhost:3000/not-found
コンソール出力
root
get_foo
post_foo
foo_bar
*   Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET /not-found HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.1.2
> Accept: */*
> 
< HTTP/1.1 404 Not Found
< content-length: 0
< date: Thu, 30 May 2024 06:59:28 GMT
< 
* Connection #0 to host localhost left intact
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

JSON 出力

serde_json が必要なようだ。

コマンド
cargo add serde_json
src/main.rs
use axum::{routing::get, Json, Router};
use serde_json::{json, Value};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/json", get(json));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn json() -> Json<Value> {
    Json(json!({ "data": 42 }))
}
src/main.rs
use axum::{routing::get, Json, Router};
use serde_json::{json, Value};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/json", get(json));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn json() -> Json<Value> {
    Json(json!({ "data": 42 }))
}
コマンド
cargo watch -x run
コマンド(別ターミナルで)
curl http://localhost:3000/json
コンソール出力
{"data":42}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

エラー処理

https://docs.rs/axum/latest/axum/#error-handling

axum ではシンプルにエラーをレスポンスに変換でき、また、すべてのエラーが処理されていることを保証できるようだ。

https://docs.rs/axum/latest/axum/error_handling/index.html

Infallible という列挙型が出てくるようだ、決して起こらないエラーのための型らしいが何に使うんだろう。

https://doc.rust-lang.org/nightly/core/convert/enum.Infallible.html

ドキュメントによると必ず Ok な Result 型を表現するのに使うようだ。

anyhow とはエラー処理のクレートのようだ。

https://docs.rs/anyhow/latest/anyhow/

Ok::<_, anyhow::Error>(Response::new(Body::empty())) という書き方はクロージャなどが関わっているのだろうか?

Rust の知識が足りなすぎて消化できない。。。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

もう一度 Hello World に戻る

src/main.rs
use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
コマンド
cargo watch -x run
コマンド(別ターミナルで)
curl http://localhost:3000/
実行結果
Hello, World!
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Captures

https://docs.rs/axum/latest/axum/struct.Router.html#captures

キャプチャーを使うと下記のようなパスにマッチさせることができる。

  • /:key
  • /users/:id
  • /users/:id/tweets

キャプチャされた値は Path と呼ばれるエクストラクターを使うことで取得できるようだ。

https://docs.rs/axum/latest/axum/extract/struct.Path.html

MatchedPath を使うことで /:key/users/:id のようなルート設定に使ったパスの文字列を取得できる。

https://docs.rs/axum/latest/axum/extract/struct.MatchedPath.html

セグメントが特定の数値や正規表現にマッチしているかどうかをチェックすることはできず、それについてはハンドラーで手動でチェックする必要があるようだ。