如果你还在用 Express 写 API,或者被 Actix-web 的宏地狱折磨过,这篇文章就是为你准备的。
一、Axum 是什么?
Axum 是 Tokio 团队官方维护的 Rust Web 框架,它的定位非常精准:一个基于 Tower 中间件生态的、类型安全的、极简主义 Web 框架。
它不是什么"瑞士军刀",而是一把打磨到极致的日式厨刀——只做一件事,把它做到最好:处理 HTTP 请求与响应。
- 开源地址:https://github.com/tokio-rs/axum
- 许可证:MIT(完全自由商用)
- 最新稳定版:0.8.x(2026 年 3 月)
- 项目状态:Tokio 官方维护,社区活跃,CNCF 技术雷达推荐
Axum 的核心设计哲学只有四个字:类型即文档。路由、参数提取、中间件、错误处理——全部用 Rust 类型系统来表达,编译器就是你的第一道防线。
二、为什么选 Axum?四大核心优势
2.1 零宏声明式路由
大多数 Rust Web 框架用宏(macro)来定义路由,比如 Actix-web 的 #[get("/users/{id}")]。这种写法的硬伤在于:宏内部出错时,编译器的报错信息让人想摔键盘。
Axum 直接抛弃了路由宏。路由用纯 Rust 函数链式组合:
let app = Router::new()
.route("/users", get(list_users).post(create_user))
.route("/users/{id}", get(get_user).delete(delete_user));
没有任何魔法。你看到的就是真相。IDE 能直接跳转,编译器错误指哪打哪——这对团队协作和大型项目的维护体验是质的变化。
2.2 类型安全的提取器(Extractor)体系
Axum 的"提取器"是它最亮眼的设计。任何从请求中提取数据的行为——路径参数、查询字符串、请求体 JSON、Header——都通过实现 FromRequest trait 来完成。
// 路径参数:/users/{id} → id: i32
async fn get_user(Path(id): Path<i32>) -> impl IntoResponse { … }
// JSON 请求体:自动反序列化 + 400 错误
async fn create_user(Json(payload): Json<CreateUserRequest>) -> impl IntoResponse { … }
// 查询字符串:/search?keyword=rust&page=2
async fn search(Query(params): Query<SearchParams>) -> impl IntoResponse { … }
// 组合提取:一行代码拿到多种数据
async fn update(
State(db): State<DbPool>,
Path(id): Path<i32>,
Json(payload): Json<UpdateRequest>,
) -> Result<Json<User>, AppError> { … }
如果一个 Extractor 组合在类型上不成立,代码根本编译不过。你在 Express 里需要跑到线上才会炸的参数校验错误,在 Axum 里,保存文件那一瞬间就已经被消灭了。
2.3 Tower 中间件生态全面接入
这是一个被严重低估的杀手级优势。
Tower 是 Rust 异步生态的中间件标准,相当于 Java 的 Servlet Filter + Netty 的 ChannelHandler 的合体。Axum 天生兼容整个 Tower 生态——你不需要等框架作者支持某个中间件,社区已经有成百上千个现成的 Tower 中间件可用:
| 中间件 | 用途 | 所需依赖 |
| tower-http::cors | CORS 跨域 | tower-http = "0.6" |
| tower-http::compression | gzip/brotli 压缩 | tower-http = "0.6" |
| tower-http::trace | 请求日志追踪 | tower-http = "0.6" |
| tower::limit::rate | 速率限制 | tower = "0.5" |
| tower::timeout | 请求超时控制 | tower = "0.5" |
| tower-http::auth | 认证/鉴权 | tower-http = "0.6" |
一行代码挂载中间件:
let app = Router::new()
.route("/api", get(handler))
.layer(TraceLayer::new_for_http()) // 自动记录每个请求
.layer(CompressionLayer::new()) // 自动压缩响应体
.layer(CorsLayer::permissive()) // 开发环境放开 CORS
.layer(TimeoutLayer::new(Duration::from_secs(30))); // 30s 超时
2.4 Tokio 亲儿子——性能与可靠性
Axum 由 Tokio 核心团队开发维护。这意味着什么?
- 零中间商赚差价:直接运行在 Tokio 异步运行时上,没有抽象层损耗
- 优先适配:Tokio 的新特性,Axum 总是第一个支持
- 长期维护承诺:Tokio 是 Rust 异步生态的地基,只要 Tokio 不凉,Axum 就不会凉
实测数据(TechEmpower 2026 基准测试):Axum 在**纯框架(无 ORM)**场景下,单机 QPS 达到 620 万,比 Express.js 快约 18 倍,比 Spring Boot 快约 7 倍。
三、适用场景:什么时候该用 Axum?
3.1 微服务 API 网关 / BFF 层(强烈推荐)
微服务架构中对性能敏感、逻辑较轻的 API 层,是 Axum 的最佳用武之地。
理由:
- 极低的资源占用(内存 ~10MB 起,对比 Spring Boot ~200MB)
- 冷启动速度极快(毫秒级,适合 Serverless/容器弹性伸缩)
- 类型安全的 API 定义,天然适配 OpenAPI / gRPC-transcoding
反例:如果你的服务层重度依赖 Spring 生态组件(如 Spring Security OAuth2 完整实现),那选 Spring Boot 更务实。
3.2 高性能实时通信服务
配合 Tokio 的异步 I/O,Axum 处理 WebSocket 长连接的能力非常亮眼:
async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
ws.on_upgrade(|socket| async move {
let (mut sender, mut receiver) = socket.split();
// 双工通信逻辑
})
}
适合聊天服务、游戏服务器、实时数据推送等场景。
3.3 边缘计算 / WASM 部署
Axum 的体积小到可以编译为 WASM 运行在 Cloudflare Workers / Fastly Compute 上,这是 JVM 和 Node.js 无法企及的。
3.4 替代老旧的 Express/Fastify 服务(渐进式迁移)
如果你的 Node.js 服务因为单线程瓶颈撑不住流量了,用 Axum 重写核心热路径是最低成本的优化方案。Rust 的内存安全保证让你不会因为忘了 await 某个 Promise 而炸线上。
四、30 分钟上手:从零到生产级 API
下面带你从 cargo init 开始,搭建一个包含数据库、认证、错误处理的完整 API 服务。
4.1 项目初始化
# Cargo.toml
[package]
name = "my-api"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }
tower-http = { version = "0.6", features = ["cors", "trace", "compression"] }
tracing = "0.1"
tracing-subscriber = "0.3"
uuid = { version = "1", features = ["v4"] }
chrono = { version = "0.4", features = ["serde"] }
4.2 定义数据模型
// src/models.rs
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use uuid::Uuid;
use chrono::{DateTime, Utc};
#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct User {
pub id: Uuid,
pub name: String,
pub email: String,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub name: String,
pub email: String,
}
#[derive(Debug, Deserialize)]
pub struct ListUsersQuery {
pub page: Option<i64>,
pub per_page: Option<i64>,
}
4.3 构建错误处理体系
// src/error.rs
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
#[derive(Debug)]
pub enum AppError {
NotFound(String),
BadRequest(String),
Internal(String),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
AppError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
};
(status, Json(json!({ "error": message }))).into_response()
}
}
// 将 sqlx::Error 转换为 AppError
impl From<sqlx::Error> for AppError {
fn from(err: sqlx::Error) -> Self {
match err {
sqlx::Error::RowNotFound => AppError::NotFound("资源不存在".into()),
_ => AppError::Internal(format!("数据库错误: {}", err)),
}
}
}
4.4 编写 Handler
// src/handlers.rs
use axum::{
extract::{Path, Query, State},
Json,
};
use uuid::Uuid;
use sqlx::PgPool;
use crate::error::AppError;
use crate::models::{CreateUserRequest, ListUsersQuery, User};
// 创建用户
pub async fn create_user(
State(db): State<PgPool>,
Json(payload): Json<CreateUserRequest>,
) -> Result<(axum::http::StatusCode, Json<User>), AppError> {
let user = sqlx::query_as::<_, User>(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *"
)
.bind(&payload.name)
.bind(&payload.email)
.fetch_one(&db)
.await?;
Ok((axum::http::StatusCode::CREATED, Json(user)))
}
// 获取单个用户(按 UUID)
pub async fn get_user(
State(db): State<PgPool>,
Path(id): Path<Uuid>,
) -> Result<Json<User>, AppError> {
let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
.bind(id)
.fetch_one(&db)
.await?;
Ok(Json(user))
}
// 分页列表
pub async fn list_users(
State(db): State<PgPool>,
Query(params): Query<ListUsersQuery>,
) -> Result<Json<Vec<User>>, AppError> {
let page = params.page.unwrap_or(1).max(1);
let per_page = params.per_page.unwrap_or(20).min(100);
let offset = (page – 1) * per_page;
let users = sqlx::query_as::<_, User>(
"SELECT * FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2"
)
.bind(per_page)
.bind(offset)
.fetch_all(&db)
.await?;
Ok(Json(users))
}
// 删除用户
pub async fn delete_user(
State(db): State<PgPool>,
Path(id): Path<Uuid>,
) -> Result<axum::http::StatusCode, AppError> {
let result = sqlx::query("DELETE FROM users WHERE id = $1")
.bind(id)
.execute(&db)
.await?;
if result.rows_affected() == 0 {
return Err(AppError::NotFound("用户不存在".into()));
}
Ok(axum::http::StatusCode::NO_CONTENT)
}
4.5 组装应用
// src/main.rs
mod error;
mod handlers;
mod models;
use axum::{Router, routing::{delete, get, post}};
use sqlx::PgPool;
use tower_http::{
cors::CorsLayer,
compression::CompressionLayer,
trace::TraceLayer,
};
use tracing_subscriber;
#[tokio::main]
async fn main() {
// 初始化日志
tracing_subscriber::fmt::init();
// 连接数据库(实际项目中改用环境变量)
let db_url = std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://localhost/myapp".to_string());
let pool = PgPool::connect(&db_url).await
.expect("数据库连接失败");
// 执行建表(生产环境请用 migration 工具)
sqlx::query(
"CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
)"
)
.execute(&pool)
.await
.expect("建表失败");
// 构建路由
let app = Router::new()
.route("/users", get(handlers::list_users).post(handlers::create_user))
.route("/users/{id}", get(handlers::get_user).delete(handlers::delete_user))
.layer(TraceLayer::new_for_http()) // 请求日志
.layer(CompressionLayer::new()) // 响应压缩
.layer(CorsLayer::permissive()) // CORS
.with_state(pool); // 注入数据库连接池
// 启动服务
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
tracing::info!("服务启动在 http://0.0.0.0:3000");
axum::serve(listener, app).await.unwrap();
}
4.6 测试一下
# 启动
DATABASE_URL=postgres://localhost/myapp cargo run
# 创建用户
curl -X POST http://localhost:3000/users \\
-H "Content-Type: application/json" \\
-d '{"name":"张三","email":"zhangsan@example.com"}'
# 响应:
# {"id":"3fa85f64-5717-4562-b3fc-2c963f66afa6","name":"张三","email":"zhangsan@example.com","created_at":"2026-07-24T08:30:00Z"}
# 获取用户
curl http://localhost:3000/users/3fa85f64-5717-4562-b3fc-2c963f66afa6
# 分页列表
curl "http://localhost:3000/users?page=1&per_page=10"
五、对比:Axum vs Actix-web vs Rocket(2026 版)
| 维度 | Axum 0.8 | Actix-web 4.x | Rocket 0.5 |
| 生态定位 | Tokio 亲儿子 | 独立运行时 | 独立框架 |
| 路由方式 | 纯函数链式调用 | 宏 #[get("/")] | 宏 #[get("/")] |
| 中间件 | Tower 全生态 | 自有体系 | 自有 Fairing |
| 异步运行时 | Tokio | Actix RT | Tokio |
| Rust 版本 | 稳定版即可 | 稳定版即可 | 需要 nightly |
| 学习曲线 | 平缓(理解 Extractor 即可) | 中等 | 平缓但受 nightly 限制 |
| 生产成熟度 | Discord / AWS / Cloudflare | 大量企业用户 | 偏社区项目 |
| GitHub Stars | 20k+ | 31k+ | 24k+ |
我的建议:
- 新项目 → Axum(没有历史包袱,选未来)
- 高性能、已有 Actix 经验且不需要 Tower 生态 → Actix-web(依然是性能标杆)
- 个人项目、愿意用 nightly Rust → Rocket(开发体验最丝滑)
- 不想折腾,只想赶紧把 API 跑起来 → Axum
六、常见避坑要点
6.1 State 必须是 Clone 的
axum::extract::State 要求其内部类型实现 Clone。数据库连接池(PgPool / MySqlPool)天然支持 Clone,因为它内部是 Arc 包装。但如果传的是自定义结构体,记得派生 Clone。
6.2 不要在 Handler 里做重度计算
Rust 的 async 模型是协作式调度。如果你的 Handler 里有 CPU 密集型操作(图片处理、密码哈希等),请用 tokio::task::spawn_blocking 把它扔到阻塞线程池。
async fn heavy_handler() -> impl IntoResponse {
let result = tokio::task::spawn_blocking(|| {
// 重度计算逻辑
heavy_computation()
})
.await
.unwrap();
Json(result)
}
6.3 错误处理:尽量统一错误类型
上面的示例已经展示了用 AppError 枚举统一错误类型的模式。不要在 Handler 里返回 Result<Json<T>, Box<dyn Error>>——这让下游客户端无法区分 400 和 500,调试起来非常痛苦。
6.4 Middleware 的 layer 顺序决定逻辑
Router::new()
.layer(TimeoutLayer::new(Duration::from_secs(10))) // 最先执行
.layer(TraceLayer::new_for_http()) // 其次
.layer(CompressionLayer::new()) // 最后
layer 的包裹顺序是洋葱模型:最外层 layer 最先处理请求、最后处理响应。超时层必须放在最外层,否则被压缩层挡住,超时控制可能失效。
七、总结
Axum 不是 Rust Web 框架里功能最多的,但它是最"Rust"的那个——充分利用类型系统做编译期检查,拒绝魔法宏,拥抱 Tower 生态,靠 Tokio 团队背书长期维护。
如果你正在做技术选型,或者想从 Node.js / Spring Boot 迁移一部分服务到 Rust,Axum 是 2026 年最稳妥的选择。
用 Axum 写 API,你不需要信任框架,你只需要信任 Rust 编译器。而后者,是整个编程世界里最值得信任的东西。
网硕互联帮助中心





评论前必须登录!
注册