ข้ามไปยังเนื้อหา

การสร้าง API ด้วย Axum

ถ้าคุณมาจาก Node.js คุณคงรู้จัก Express หรือ NestJS แล้ว Axum คือคำตอบของ Rust สำหรับพื้นที่นั้น — เป็น web framework ระดับสูงที่ให้คุณเขียน async fn route handlers, วิเคราะห์ข้อมูล request ด้วย type-safe extractors และแชร์ state ข้ามหลาย routes

แต่ต่างจาก Express ตรงที่ Axum ไม่ได้รวมทุกอย่างมาในตัว ออกแบบมาให้บางแล้วโยนหน้าที่ middleware ให้ Tower ที่เป็น middleware library ระดับ production ที่ compose ได้ยืดหยุ่นมาก ผลที่ได้คือ:

  • Type safety ตั้งแต่ compile-time ตลอดทั้ง request/response cycle
  • Zero-cost abstractions — ไม่มี garbage collector ไม่มีการ allocation ที่ซ่อนอยู่
  • Async แบบ first-class สร้างบน Tokio ที่เป็น async runtime มาตรฐานของ Rust
ฟีเจอร์ExpressNestJSAxum
ภาษาTypeScript / JSTypeScriptRust
Routingapp.get(...)@Get(...) decoratorRouter::new().route(...)
Middlewareapp.use(...)@Middleware / guardsTower layers
วิเคราะห์ Requestreq.body, req.params@Body(), @Param()Extractors (Json, Path, Query)
Stateapp.localsDependency injectionState(...) extractor
จัดการ Errornext(err) middlewareException filtersResult<T, E> + IntoResponse
Type safetyบางส่วน (runtime)บางส่วน (runtime)เต็มรูปแบบ (compile-time)
TypeScript
// Express
import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.json({ message: 'Hello, World!' });
});
app.listen(3000, () => console.log('Listening on :3000'));
Rust
use axum::{routing::get, Json, Router};
use serde_json::{json, Value};
async fn hello() -> Json<Value> {
Json(json!({ "message": "Hello, World!" }))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(hello));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
println!("Listening on :3000");
axum::serve(listener, app).await.unwrap();
}

รันบนเครื่องของคุณ — Axum ผูก port และทำงานตลอดเวลา จึงไม่สามารถรันใน browser sandbox ได้ ใช้ cargo run หลังจากตั้งค่าโปรเจกต์ในบทถัดไป

ตลอดโมดูลนี้คุณจะสร้าง Users API ขนาดเล็กที่ประกอบด้วย:

  • GET /users — ดึงรายการ users (pagination ด้วย Query)
  • POST /users — สร้าง user ใหม่ (Json body)
  • GET /users/{id} — ดึง user เดียว (Path)
  • DELETE /users/{id} — ลบ user
  • Shared in-memory state (Arc<Mutex<Vec<User>>>)
  • JSON errors แบบมีโครงสร้าง
  • Tower CORS layer
  • SQLx database integration (เชิงแนวคิด)
  • Handler tests ด้วย tower::ServiceExt::oneshot

แต่ละแนวคิดเชื่อมโยงโดยตรงกับสิ่งที่คุณรู้จากอยู่แล้วจาก Express หรือ NestJS

โมดูลนี้ใช้ Axum 0.8 ถ้าคุณกำลังย้าย codebase จาก 0.7 หรืออ่าน tutorial เก่า ๆ อยู่ นี่คือจุดที่จะทำให้ code พัง:

0.70.8
.route("/users/:id", ...).route("/users/{id}", ...) — และ /*rest กลายเป็น /{*rest}
ต้องมี #[async_trait] บน FromRequestPartsไม่ต้องแล้ว trait ใช้ async fn แบบ native
Option<Path<T>> กลืน error ทุกกรณีตอนนี้ reject request ในกรณีส่วนใหญ่ — ถ้าต้องการพฤติกรรมเดิมให้ implement OptionalFromRequestParts
extractor Host อยู่ใน axumย้ายไป crate axum-extra
Message::Text(String)Message::Text(Utf8Bytes) และ Message::Binary(Bytes)
re-export axum::body::{Empty, Full}ใช้ axum::body::Body

handler และ service ต้องเป็น Sync ด้วย ส่วนที่เหลือของ API — extractor, State, Tower layer — ไม่เปลี่ยน การย้ายจึงเกือบทั้งหมดเป็นแค่การ find-and-replace ที่ route string

Axum ใช้ async runtime ใด?
Axum จัดการ middleware อย่างไรเมื่อเทียบกับ Express?
อะไรคือสิ่งที่แทน `req.body` และ `req.params` ใน Axum?