axum を学び直す
今回から何回かに渡って axum crate について学び直そうと思います。
axum とは
Rust の Web アプリケーションフレームワークです。
https://github.com/tokio-rs/axum
今回はバージョン 0.8.7 を対象に調べていきます。既に 0.9 に向けて開発は進んでいるので、リポジトリを参照する場合はブランチを指定すると良いと思います。
https://docs.rs/axum/0.8.7/axum/index.html
README や crate ドキュメントには次のようにあります。
High-level features
- Route requests to handlers with a macro-free API.
- Declaratively parse requests using extractors.
- Simple and predictable error handling model.
- Generate responses with minimal boilerplate.
- Take full advantage of the tower and tower-http ecosystem of middleware, services, and utilities.
- リクエストをハンドラーにマクロなしの API でルーティングする
- Extractor で宣言的にリクエストを解釈する
- シンプルで予測可能なエラーハンドリング
- 最小限のボイラープレートでレスポンスを生成する
-
towercrate とtower-httpcrate の middleware を採用する
実際に触ったことがあれば「そうだね」で終わる説明ですね。実際に触ってみて判断したほうが良さそうです。
crate ドキュメントの見出しからいくつか抜粋すると次のようになります。
- Routing (
Routerstruct) - Handlers (
handlermod) - Extractors (
extractmod) - Responses (
responsemod) - Error handling (
error_handlingmod) - Middleware (
middlewaremod)
この流れで深堀していこうと思いますが、今回はかんたんな例でひとまわりしようかと思います。
かんたんな例
https://github.com/bouzuya/rust-examples/tree/bbd2ff57733a7f48a95f8dce2c177fa466a960fd/axum4
cargo new axum4 など、適当にプロジェクトを作ります。
Cargo.toml
[package]
name = "axum4"
edition = "2024"
publish = false
[dependencies]
axum = "0.8.7"
serde_json = "1.0.145"
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] }
src/main.rs
async fn root() -> &'static str {
"Hello, world!"
}
async fn get_user(
axum::extract::Path(user_id): axum::extract::Path<u32>,
) -> Result<String, axum::http::StatusCode> {
if user_id == 0 {
Err(axum::http::StatusCode::NOT_FOUND)
} else {
Ok(format!("User {}", user_id))
}
}
async fn create_user(
axum::extract::Json(json): axum::extract::Json<serde_json::Value>,
) -> axum::response::Json<serde_json::Value> {
// ...
axum::response::Json(json)
}
#[tokio::main]
async fn main() {
let app = axum::Router::new()
.route("/", axum::routing::get(root))
.route("/users", axum::routing::post(create_user))
.route("/users/{user_id}", axum::routing::get(get_user));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
これを cargo run で動かして、 curl で呼び出すとこういう感じ。
$ curl -D - localhost:3000/
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 13
date: Thu, 28 Aug 2025 12:53:00 GMT
Hello, world!
$ curl -D - localhost:3000/users/0
HTTP/1.1 404 Not Found
content-length: 0
date: Thu, 28 Aug 2025 12:53:09 GMT
$ curl -D - localhost:3000/users/123
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 8
date: Thu, 28 Aug 2025 12:53:12 GMT
User 123
$ curl -D - --json '{"name":"bouzuya"}' localhost:3000/users
HTTP/1.1 200 OK
content-type: application/json
content-length: 18
date: Thu, 28 Aug 2025 12:53:31 GMT
{"name":"bouzuya"}
例の説明
この記事では import を避けています。これはエディタなどの補助がない場合、型の区別がつきづらいためです。 import すると、たとえば axum::extract::Path(id) は Path(id) のように短く書けます。
Routing
Router struct はルーティングを設定する構造体です。
let app = axum::Router::new()
.route("/", axum::routing::get(root))
.route("/users", axum::routing::post(create_user))
.route("/users/{user_id}", axum::routing::get(get_user));
基本は、パスパターンとメソッドとハンドラーを指定します。
Handler 以外に tower::Service を実装したものへのルーティングもできます。
Handlers
Handler はリクエストを処理してレスポンスを返します。
async fn root() -> &'static str {
"Hello, world!"
}
Extractors
Extractor は Handler の引数に指定し、リクエストからデータを抽出します。パス・クエリ文字列・ヘッダー・ボディなどから指定した形式で読み取ります。
async fn get_user(
axum::extract::Path(user_id): axum::extract::Path<u32>,
) -> Result<String, axum::http::StatusCode> {
if user_id == 0 {
Err(axum::http::StatusCode::NOT_FOUND)
} else {
Ok(format!("User {}", user_id))
}
}
Responses
Response は Handler の返り値に指定し、指定した形式でレスポンスを返します。
IntoResponse トレイトを実装していれば良いので、前述の &'static str のようなものも返せます。
async fn create_user(
axum::extract::Json(json): axum::extract::Json<serde_json::Value>,
) -> axum::response::Json<serde_json::Value> {
// ...
axum::response::Json(json)
}
Error handling
エラーハンドリングはレスポンスの話の一部とも言えます。
IntoResponse を実装し Err 側の値に IntoResponse を実装しておけば良いだけです。
独自のエラーの定義もできます。
Middleware
axum::serve は listener と make_service を引数に取ります。 make_service は tower::Service を取ります。
Router も実は tower::Service を実装しています。
#[tokio::main]
async fn main() {
let app = axum::Router::new()
.route("/", axum::routing::get(root))
.route("/users", axum::routing::post(create_user))
.route("/users/{user_id}", axum::routing::get(get_user));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
おわりに
今回はとりあえず axum の crate ドキュメントの見出し (構成要素) と、かんたんな例を示しました。
次回以降は各要素を深堀してみようと思います。
参考
- axum - crates.io: Rust Package Registry https://crates.io/crates/axum
- axum - Rust https://docs.rs/axum/0.8.7/axum/index.html
- tower - crates.io: Rust Package Registry https://crates.io/crates/tower
- tower-http - crates.io: Rust Package Registry https://crates.io/crates/tower-http
Discussion