はじめに
本記事では、Rust Axum ルーティング ミドルウェアをテーマに、複数ルートの定義からロギングミドルウェアの実装までを解説します。シリーズの第2回として、第1回で作った最小構成の API を、より実践的な形に育てていく内容です。
本シリーズで予定している内容は以下の通りです。
- 第1回:Rust×Axumで作るREST API ① セットアップ〜Hello Worldまで
- 第2回(本記事):ルーティング・ミドルウェアの実装
- 第3回:SeaORMでDB接続・CRUDを完成させる
第1回をまだ読んでいない方は、先にこちらでサーバーを起動できる状態にしておいてください。

まずは、今回実装するエンドポイントを整理します。第1回の /hello に加え、以下のルートを追加していきます。
GET /health:サーバーの稼働確認用エンドポイントGET /users/:id:パスパラメータでユーザーIDを受け取るエンドポイントGET /api/items:プレフィックス/apiを持つ複数ルートのグルーピング例
加えて、すべてのリクエストにロギングミドルウェアを適用し、アクセスログをコンソールに出力できるようにします。これにより、開発中の動作確認やデバッグが格段にしやすくなります。
依存クレートの追加
まずは、ロギング用のクレートを Cargo.toml に追加します。Axum 自体には変更はありません。
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
tower-http = { version = "0.5", features = ["trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
追加した3つのクレートの役割は以下の通りです。
tower-http:Axum と組み合わせて使えるミドルウェア集。今回はその中のTraceLayerを利用しますtracing:構造化ログを扱うための基盤クレートtracing-subscriber:tracingが出力するログを実際にコンソールへ書き出すサブスクライバ
なお、tower-http の詳細は公式ドキュメントを参考にしてください。
複数ルートを定義する
次は、ルーターに複数のエンドポイントを定義していきます。第1回の main.rs を、以下のように書き換えます。
use axum::{routing::get, Router};
#[tokio::main]
async fn main() {
// 複数のルートをチェーンで定義
let app = Router::new()
.route("/hello", get(hello))
.route("/health", get(health));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
println!("サーバーを起動しました: http://127.0.0.1:3000");
axum::serve(listener, app).await.unwrap();
}
// /hello ハンドラ
async fn hello() -> &'static str {
"Hello, World!"
}
// /health ハンドラ:監視ツールからの稼働確認に使う
async fn health() -> &'static str {
"OK"
}
このように、Router::new() に .route(...) をチェーンするだけでルートを増やせます。ルートが増えてきたら、後述するグルーピングを使って整理します。
パスパラメータを受け取る
続いて、/users/:id のようにパスの一部を変数として受け取るルートを追加します。Axum では Path 抽出器を使うことで、パスパラメータを型安全に取り出せます。
以下の処理を src/main.rs に追記します。
use axum::{extract::Path, routing::get, Router};
// /users/:id ハンドラ
async fn get_user(Path(id): Path<u32>) -> String {
// id は自動で u32 にパースされる
format!("ユーザーID: {} の情報を返します", id)
}
そして、main 関数内のルーター定義に1行追加します。
let app = Router::new()
.route("/hello", get(hello))
.route("/health", get(health))
.route("/users/:id", get(get_user));
これで、/users/123 にアクセスすると「ユーザーID: 123 の情報を返します」と返るようになります。なお、:id 部分が数値に変換できない場合、Axum が自動で 400 Bad Request を返してくれるため、自前でバリデーションコードを書く必要はありません。
ルートをグループ化する
ルートが増えてくると、関連するエンドポイントをまとめたくなります。Axum では Router::nest を使うことで、共通プレフィックスを持つルートをサブルーターとして切り出せます。
以下のように、/api 配下のルートをまとめます。
// /api 以下のルートを別ルーターで定義
fn api_routes() -> Router {
Router::new()
.route("/items", get(list_items))
.route("/items/:id", get(get_item))
}
async fn list_items() -> &'static str {
"アイテム一覧を返します"
}
async fn get_item(Path(id): Path<u32>) -> String {
format!("アイテムID: {} の詳細を返します", id)
}
そして、main 関数のルーターに nest でぶら下げます。
let app = Router::new()
.route("/hello", get(hello))
.route("/health", get(health))
.route("/users/:id", get(get_user))
.nest("/api", api_routes());
これで、/api/items と /api/items/:id の2つのエンドポイントが、サブルーター経由で利用できるようになります。ルート定義を機能ごとにファイル分割したくなったときも、この nest が起点になります。
ロギングミドルウェアを組み込む
最後に、すべてのリクエストをログ出力するミドルウェアを追加します。Axum におけるミドルウェアは、tower という抽象に従って実装されており、Router の .layer() メソッドで適用できます。
まず、main の冒頭で tracing-subscriber を初期化し、TraceLayer を .layer() でルーター全体に適用します。
完成形の src/main.rs は以下の通りです。
use axum::{extract::Path, routing::get, Router};
use tower_http::trace::TraceLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() {
// ロガーの初期化(環境変数 RUST_LOG に従う。未設定時は info レベル)
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "axum_rest_api=debug,tower_http=debug".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
let app = Router::new()
.route("/hello", get(hello))
.route("/health", get(health))
.route("/users/:id", get(get_user))
.nest("/api", api_routes())
// すべてのリクエストにアクセスログを付与する
.layer(TraceLayer::new_for_http());
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
tracing::info!("サーバーを起動しました: http://127.0.0.1:3000");
axum::serve(listener, app).await.unwrap();
}
// ... ハンドラ定義は省略
これで、Rust Axum ルーティング ミドルウェアの基本構成が整いました。cargo run でサーバーを起動し、curl http://127.0.0.1:3000/users/1 などを実行すると、コンソールに以下のようなログが流れます。
2026-05-09T01:23:45.678Z DEBUG request{method=GET uri=/users/1 version=HTTP/1.1}: tower_http::trace::on_request: started processing request
2026-05-09T01:23:45.679Z DEBUG request{method=GET uri=/users/1 version=HTTP/1.1}: tower_http::trace::on_response: finished processing request latency=0 ms status=200
リクエストごとにメソッド・パス・処理時間・ステータスコードが出力されるため、開発中のデバッグや本番のアクセス解析にそのまま活用できます。

動作確認
実装が一通り終わったので、追加したエンドポイントを順に確認していきます。まずは、サーバーを起動します。
cargo run
次に、別のターミナルから各エンドポイントへアクセスします。
curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/users/42
curl http://127.0.0.1:3000/api/items
curl http://127.0.0.1:3000/api/items/7
それぞれ、以下のレスポンスが返れば成功です。
OK
ユーザーID: 42 の情報を返します
アイテム一覧を返します
アイテムID: 7 の詳細を返します
サーバー側のターミナルにもリクエストごとのアクセスログが流れているはずです。両方が確認できれば、ルーティングとミドルウェアが正しく動いていることが分かります。
まとめ
今回は、Rust Axum ルーティング ミドルウェアをテーマに、複数ルートの定義・パスパラメータ・サブルーターによるグルーピング・TraceLayer によるロギングまでを実装しました。これにより、シリーズの API が実用的な構造に近づき、リクエストの流れも目で追える状態になりました。
次回は、SeaORM を組み合わせて DB 接続層を実装し、CRUD エンドポイントを完成させていきます。ORM の基礎を先に押さえておきたい方は、Rust×SeaORMで学ぶORM入門も合わせてご覧ください。

