Tasuke HubLearn · Solve · Grow
#Rust

Tokio使いのための次世代Webフレームワーク「Axum」完全実装ガイド

Actix-webより書きやすく、Towerエコシステムと完全統合。Rust非同期プログラミングの経験者が最短でプロダクション品質のAPIを構築する方法を解説します。

時計のアイコン26 November, 2025
TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

Rustでのバックエンド開発、どのフレームワーク?

Rustでバックエンドを書くなら、選択肢は主に3つです:

  1. Actix-web: 最速だが、独自のActorモデルで癖が強い
  2. Rocket: シンプルだが、非同期対応が遅れていた
  3. Axum: Tokioネイティブで、エルゴノミクスと性能を両立

Tokio/Towerのエコシステムに慣れているなら、Axumは最良の選択です。

ベストマッチ

最短で課題解決する一冊

この記事の内容と高い親和性が確認できたベストマッチです。早めにチェックしておきましょう。

Axumの特徴

1. Towerミドルウェアとの完璧な統合

Axumはtowertower-httpのミドルウェアがそのまま使えます

use axum::{Router, routing::get};
use tower::ServiceBuilder;
use tower_http::{trace::TraceLayer, compression::CompressionLayer};
use std::time::Duration;

let app = Router::new()
    .route("/", get(|| async { "Hello!" }))
    .layer(
        ServiceBuilder::new()
            .layer(TraceLayer::new_for_http())
            .layer(CompressionLayer::new())
            .timeout(Duration::from_secs(10))
    );

2. エクストラクタパターン

リクエストからのデータ抽出が、型安全かつ宣言的です。

use axum::{
    extract::{Path, Query, Json},
    http::StatusCode,
};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Pagination {
    page: Option<u32>,
    limit: Option<u32>,
}

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

async fn get_user(
    Path(user_id): Path<u32>,
    Query(pagination): Query<Pagination>,
) -> Result<Json<User>, StatusCode> {
    // idやpaginationが既に型付けされた状態で使える
    Ok(Json(User {
        id: user_id,
        name: "Alice".to_string(),
    }))
}

さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

実践:JWT認証付きREST API構築

実用的なCRUD APIを、認証込みで実装してみましょう。

プロジェクトセットアップ

# Cargo.toml
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["trace", "cors"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
jsonwebtoken = "9"
tracing = "0.1"
tracing-subscriber = "0.3"
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] }

JWT認証ミドルウェア

use axum::{
    async_trait,
    extract::{FromRequestParts, TypedHeader},
    headers::{Authorization, authorization::Bearer},
    http::{request::Parts, StatusCode},
    RequestPartsExt,
};
use jsonwebtoken::{decode, DecodingKey, Validation};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,
    exp: usize,
}

struct AuthUser(Claims);

#[async_trait]
impl<S> FromRequestParts<S> for AuthUser
where
    S: Send + Sync,
{
    type Rejection = StatusCode;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        let TypedHeader(Authorization(bearer)) = parts
            .extract::<TypedHeader<Authorization<Bearer>>>()
            .await
            .map_err(|_| StatusCode::UNAUTHORIZED)?;

        let token_data = decode::<Claims>(
            bearer.token(),
            &DecodingKey::from_secret("secret".as_ref()),
            &Validation::default(),
        )
        .map_err(|_| StatusCode::UNAUTHORIZED)?;

        Ok(AuthUser(token_data.claims))
    }
}

APIハンドラー

use axum::{
    extract::State,
    routing::{get, post},
    Json, Router,
};

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

// 認証不要
async fn health_check() -> &'static str {
    "OK"
}

// 認証必須
async fn get_profile(
    AuthUser(claims): AuthUser,
    State(state): State<AppState>,
) -> Result<Json<User>, StatusCode> {
    let user = sqlx::query_as::<_, User>(
        "SELECT id, name FROM users WHERE id = $1"
    )
    .bind(&claims.sub)
    .fetch_one(&state.db)
    .await
    .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok(Json(user))
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    let db = sqlx::postgres::PgPoolOptions::new()
        .max_connections(5)
        .connect("postgresql://localhost/mydb")
        .await
        .unwrap();

    let state = AppState { db };

    let app = Router::new()
        .route("/health", get(health_check))
        .route("/profile", get(get_profile))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();

    axum::serve(listener, app).await.unwrap();
}

さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

パフォーマンス最適化

1. 非同期処理の注意点

use tokio::task;

// ❌ CPU集約的な処理をそのまま実行(Tokioランタイムをブロック)
async fn bad_handler() -> String {
    let result = expensive_computation(); // NG!
    result
}

// ✅ spawn_blockingでブロッキング処理を分離
async fn good_handler() -> String {
    task::spawn_blocking(|| expensive_computation())
        .await
        .unwrap()
}

2. データベース接続プール

// 接続数を適切に設定
let pool = sqlx::postgres::PgPoolOptions::new()
    .max_connections(50)
    .min_connections(5)
    .acquire_timeout(Duration::from_secs(3))
    .connect("postgresql://localhost/mydb")
    .await?;

さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

まとめ

Axumは、Rustの型システムとTokioのエコシステムを最大限活用した、モダンなWebフレームワークです。

Actix-webの速度に近く、Rocketのエルゴノミクスを持ち、Towerの柔軟性を完全に統合しています。

Tokio/async Rustに慣れているなら、Axumで快適なバックエンド開発を体験してみませんか?

さらに理解を深める参考書

関連記事と相性の良い実践ガイドです。手元に置いて反復しながら進めてみてください。

この記事をシェア

続けて読みたい記事

編集部がピックアップした関連記事で学びを広げましょう。

#Hono

Expressより10倍速い!エッジランタイムのための超軽量フレームワーク「Hono」

2025/11/26
#AIエージェント

【2025年完全版】AIエージェントフレームワーク徹底比較:最適な選択ガイド

2025/11/28
#Kotlin

Kotlin製サーバーサイドフレームワークKtor入門:設計思想から実践的なAPI開発まで

2025/9/18
#Qwik

ハイドレーションはもう古い!Qwikフレームワークで実現する「Resumability」革命【2025年版】

2025/11/26
#WebGPU

WebGPUで動くブラウザ完結LLM実装ガイド【2025年最新】

2025/11/26
#AI

AIエージェント開発戦国時代!主要フレームワーク徹底比較【2025年版】

2025/9/3