axum

Rust の Web フレームワーク。tokio チームが開発。tower の Service トレイトをそのまま基盤に使う 設計により、ミドルウェアやエコシステムを tower / hyper / tonic 等と共有できるのが最大の特徴。

ポジショニング

フレームワーク設計思想tower 統合
axumtower ネイティブ。エコシステム共有を最優先完全
actix-web独自ランタイム(actix-rt)。性能ベンチマーク志向なし
rocket人間工学重視。マクロで宣言的 APIなし

axum は「独自のものを作らない」設計。リクエスト処理は tower の Service、抽出は FromRequest トレイト、ランタイムは tokio、HTTP は hyper。フレームワーク固有のプリミティブが少ないため、ロックインが小さい。

アーキテクチャ: tower スタック

クライアント → [tower ミドルウェア層] → [axum Router] → [ハンドラ]
                 │                          │              │
                 ├─ tower-http              ├─ ルーティング  ├─ FromRequest で抽出
                 ├─ tower::timeout          │               ├─ IntoResponse で変換
                 ├─ tower::limit            │               └─ 業務ロジック
                 └─ カスタム Service         │
                                            └─ ネストとマージが可能

axum の Router 自体が tower::Service を実装している。つまり axum アプリ全体が 1 つの Service であり、他の tower Service と自由に合成できる。

基本的な構造

use axum::{Router, routing::get, Json, extract::Path};
use serde::Serialize;

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

async fn get_user(Path(id): Path<u64>) -> Json<User> {
    Json(User { id, name: "Alice".into() })
}

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

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

Extractor(抽出)

リクエストから値を取り出す仕組み。ハンドラの引数に型を書くだけで自動的に抽出される。FromRequest / FromRequestParts トレイトの実装。

Extractor抽出元例
Path<T>URL パスパラメータ/users/{id} → Path(id): Path<u64>
Query<T>クエリ文字列?page=1 → Query(params): Query<Pagination>
Json<T>JSON リクエストボディJson(body): Json<CreateUser>
Form<T>フォームデータForm(data): Form<LoginForm>
State<T>アプリケーション状態State(db): State<DbPool>
Extension<T>ミドルウェアが注入した値Extension(user): Extension<CurrentUser>
TypedHeader<T>型付きヘッダTypedHeader(auth): TypedHeader<Authorization<Bearer>>
Request生リクエスト全体フォールバック用

カスタム Extractor は FromRequest / FromRequestParts を実装するだけで作れる。

// カスタム Extractor の例
#[async_trait]
impl<S> FromRequestParts<S> for CurrentUser
where S: Send + Sync,
{
    type Rejection = AuthError;

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        // ヘッダから JWT を取り出して検証
    }
}

// ハンドラで使う
async fn dashboard(user: CurrentUser) -> impl IntoResponse { ... }

State 管理

アプリケーション共有状態は Router::with_state() で注入し、State<T> Extractor で取り出す。

#[derive(Clone)]
struct AppState {
    db: PgPool,
    redis: RedisPool,
}

let app = Router::new()
    .route("/users", get(list_users))
    .with_state(AppState { db, redis });

async fn list_users(State(state): State<AppState>) -> Json<Vec<User>> {
    let users = sqlx::query_as("SELECT * FROM users")
        .fetch_all(&state.db).await.unwrap();
    Json(users)
}

State は Clone を要求する。DB プールや設定など共有可能なものを入れる。ミュータブルな共有状態が必要なら Arc<Mutex<T>> や Arc<RwLock<T>>。

ミドルウェア

tower / tower-http のミドルウェアをそのまま使える。axum 固有のミドルウェア層は持たない。

use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;
use tower::limit::RateLimitLayer;

let app = Router::new()
    .route("/api/users", get(list_users))
    .layer(CorsLayer::permissive())
    .layer(TraceLayer::new_for_http())
    .layer(RateLimitLayer::new(100, Duration::from_secs(60)));

主要な tower-http ミドルウェア:

ミドルウェア機能
CorsLayerCORS ヘッダ制御
TraceLayerリクエスト/レスポンスのトレーシング
CompressionLayergzip / br / zstd 圧縮
TimeoutLayerリクエストタイムアウト
SetRequestHeaderLayerヘッダ追加
CatchPanicLayerpanic をキャッチして 500 に変換

Router の合成

Router はネスト(nest)とマージ(merge)で構成できる。大規模アプリではモジュールごとに Router を作り、最後にマージする。

fn user_routes() -> Router<AppState> {
    Router::new()
        .route("/", get(list_users).post(create_user))
        .route("/{id}", get(get_user).put(update_user).delete(delete_user))
}

fn admin_routes() -> Router<AppState> {
    Router::new()
        .route("/stats", get(stats))
        .layer(admin_auth_layer())  // このルートグループだけにミドルウェア適用
}

let app = Router::new()
    .nest("/api/users", user_routes())
    .nest("/admin", admin_routes())
    .with_state(state);

エラーハンドリング

ハンドラは IntoResponse を実装する任意の型を返せる。Result<T, E> も E: IntoResponse なら返せる。

enum AppError {
    NotFound,
    Internal(anyhow::Error),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        match self {
            AppError::NotFound => StatusCode::NOT_FOUND.into_response(),
            AppError::Internal(e) => {
                tracing::error!("{:?}", e);
                StatusCode::INTERNAL_SERVER_ERROR.into_response()
            }
        }
    }
}

async fn get_user(Path(id): Path<u64>) -> Result<Json<User>, AppError> {
    let user = db.find(id).await.map_err(AppError::Internal)?;
    user.ok_or(AppError::NotFound).map(Json)
}

WebSocket

axum は WebSocket をネイティブサポート。

use axum::extract::ws::{WebSocket, WebSocketUpgrade};

async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
    ws.on_upgrade(handle_socket)
}

async fn handle_socket(mut socket: WebSocket) {
    while let Some(Ok(msg)) = socket.recv().await {
        socket.send(msg).await.ok();
    }
}

依存スタック

axum
├── tokio       … 非同期ランタイム(マルチスレッド work-stealing executor)
├── hyper       … HTTP 1/2 実装
├── tower       … Service トレイト + ミドルウェアフレームワーク
└── tower-http  … HTTP 固有のミドルウェア集(CORS, 圧縮, トレース等)

各層が独立したクレート。axum はこれらを糊付けする薄いレイヤーに過ぎない。

クレート役割axum との関係
tokio非同期ランタイム。タスクスケジューリング、I/O、タイマーaxum のランタイム。#[tokio::main] で起動
hyperHTTP プロトコル実装。低レベルaxum が内部で使用。ユーザーは通常意識しない
towerService トレイト定義。リクエスト→レスポンスの抽象axum の設計基盤。Router 自体が Service
tower-httpHTTP 向けミドルウェア集axum の .layer() でそのまま使う

tower::Service トレイト

axum を理解する鍵。tower の Service はリクエストを受けてレスポンスを返す非同期関数の抽象:

pub trait Service<Request> {
    type Response;
    type Error;
    type Future: Future<Output = Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
    fn call(&mut self, req: Request) -> Self::Future;
}

axum の Router、ミドルウェア、ハンドラがすべてこの Service を実装する。これにより:

他の Rust Web フレームワークとの詳細比較

axumactix-webrocket
ランタイムtokioactix-rt (tokio ベース)tokio
ミドルウェアtower 標準独自 (Transform トレイト)独自 (Fairing)
抽出FromRequest トレイトFromRequest トレイトマクロベース
型安全ルーティングコンパイル時チェックコンパイル時チェックマクロで宣言
WebSocketネイティブネイティブ外部クレート
エコシステム共有tower / hyper / tonic独自エコシステム独自エコシステム
gRPC 共存tonic と Router をマージ可能別プロセスが一般的別プロセスが一般的
学習曲線中(tower の理解が深めると有利)中低(マクロが隠蔽)

axum の最大の利点は gRPC (tonic) と HTTP を同一プロセスで共存 できること。両方とも tower::Service なので Router をマージするだけ。

押さえどころ(カード化候補)

関連

Graph