さわりだけAxum

By jasmify , 27 7月 2025

特に作りたいものがなかったので、積んである本の中から面白そうなのを選んで、プログラミングをやってみようと思ったわけです。

その本が、「Webアプリ開発で学ぶ Rust言語入門」。

axumを使ってTodoアプリを作ってみよう、ってやつ。面白そうでしょ。Webアプリ初心者にはぴったりな感じがします。本にも入門って書いてあるからね。

しかし、ツンドク期間が長すぎて、現行バージョンのaxumではコードが動かなくなっています。

あー、いきなり挫折しそう。と思ったけど、昔よりはコードを読めるようになったので、勉強を兼ねて現行バージョンの**axum = "0.8.1"**で動くように修正しながらやっていこうと思います。

本のコードは著作権にひっかかりそうなので、私が修正した部分のコードをメインに載せていきます。

著者のGitHubはこちら

axum::Serverを使えない

axum = "0.8.1"では、axum::Serverが使えなくなっています。

だから、mainは大幅に書き換える必要があります。

tokio::net::TcpListener::bind("127.0.0.1:3000")
axum::serve(listener, app).await.unwrap();

これに変えると何とか動くようにはなったかな。

unwrap()が気に入らないなら、処理はお好きなように。

GitHub - tokio-rs/axum: Ergonomic and modular web framework built with Tokio, Tower, and Hyper

↑を見るとexampleがたくさん載っているので、詳しく知りたい場合はここのコードを見るといいかもね。

axum - Rust

↑axumのドキュメントはここ。

main.rs

use axum::{
    Json, Router,
    http::StatusCode,
    routing::{get, post},
};
use serde::{Deserialize, Serialize};
#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();
    let app = create_app();
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    tracing::info!("Listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}
fn create_app() -> Router {
    Router::new()
        .route("/", get(root))
        .route("/users", post(create_user))
}
async fn root() -> &'static str {
    "Hello, World!"
}
async fn create_user(Json(payload): Json<CreateUser>) -> (StatusCode, Json<User>) {
    let user = User {
        id: 1,
        username: payload.username,
    };
    tracing::info!("Created user: {:?}", user);
    (StatusCode::CREATED, Json(user))
}

テスト用のコードも動かない

let bytes = hyper::body::to_bytes(res.into_body()).await.unwrap();

hyperを使ったこのコードですが、エラーが出ます。

だから、hyperを使わずに、

use http_body_util::BodyExt;
let bytes = res.into_body().collect().await.unwrap().to_bytes();

VSCodeで見ていると、collect()の部分でhttp_body_util::BodyExtを使ってるっぽいですね。

このテスト用コードでは、.await.unwrap()をしておかないと、戻り値を設定する必要があって結構面倒かも。

とりあえず、動くようになったよーっていうコードは下記の通りです。

main.rs(テスト用コード)

#[cfg(test)]
mod test {
    use super::*;
    use axum::{
        body::Body,
        http::{Method, Request, header},
    };
    use http_body_util::BodyExt;
    use tower::ServiceExt;
    #[tokio::test]
    async fn should_return_hello_world() {
        let req = Request::builder()
            .method(Method::GET)
            .uri("/")
            .body(Body::empty())
            .unwrap();
        let res = create_app().oneshot(req).await.unwrap();
        let bytes = res.into_body().collect().await.unwrap().to_bytes();
        let body = String::from_utf8(bytes.to_vec()).unwrap();
        assert_eq!(body, "Hello, World!");
    }
    #[tokio::test]
    async fn should_return_user_data() {
        let req = Request::builder()
            .method(Method::POST)
            .uri("/users")
            .header(header::CONTENT_TYPE, "application/json")
            .body(Body::from(r#"{"username":"alice"}"#))
            .unwrap();
        let res = create_app().oneshot(req).await.unwrap();
        let bytes = res.into_body().collect().await.unwrap().to_bytes();
        let body = String::from_utf8(bytes.to_vec()).unwrap();
        let user: User = serde_json::from_str(&body).unwrap();
        assert_eq!(
            user,
            User {
                id: 1,
                username: "alice".to_string()
            }
        );
    }
}

これでHello,World!できるようになった。やったね。

引数の順序が大事

p120, p128

エラーは、ファイルを分割する前から出ますが、

分かりやすくするために、修正したファイル分割後のコードを載せておきます。

handlers.rs(修正後)

use axum::{
    extract::Extension,
    Json,
    http::StatusCode,
};
use std::sync::Arc;
use crate::repositories::{CreateTodo, Todo, TodoRepository};

pub async fn create_todo<T: TodoRepository>(
    Extension(repository): Extension<Arc<T>>,
    Json(payload): Json<CreateTodo>,
) -> (StatusCode, Json<Todo>) {
    let todo = repository.create(payload);

    (StatusCode::CREATED, Json(todo))
}

エラーが出る場所

以下は、本に記載されているコード。

pub async fn create_todo<T: TodoRepository>(
    Json(payload): Json<CreateTodo>,
    Extension(repository): Extension<Arc<T>>,
)

Extensionを第一引数にしないとエラーが出る仕様に変更されたっぽいです。

コメント