今天你會掌握:
無論是開發手機 App 後端、微服務,還是現代化的網頁應用,這些都是必備技能。
用餐廳當比喻:
用生活化的例子幫助理解,路由就像大樓的電梯系統:
每個按鈕(路徑)都對應特定目的地(處理函數)
use axum::{Router, routing::get};
// 路由的三要素:
// 1. 路徑 (Path) : "/users"
// 2. 方法 (Method) ; GET, POST, PUT, DELETE等
// 3. 處理器 (Handler) : 實際執行的函數
let app = Router::new()
.route("/", get(home)) // 路徑 "/" + GET 方法 + home 處理器
.route("/api/users", get(list_users).post(create_user)); // 一個路徑可以有多個方法!
讓我們從最簡單的範例開始
use axum::{
Router,
routing::get,
};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(home))
.route("/about", get(about));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
println!("服務器運作中 http://127.0.0.1:3000");
axum::serve(listener, app).await.unwrap();
}
async fn home() -> &'static str {
"歡迎來到首頁!"
}
async fn about() -> &'static str {
"這是關於頁面"
}
相當單純易懂,大家應該能掌握程式如何運作。
可以瀏覽 http://127.0.0.1:3000/ 和 http://127.0.0.1:3000/about,看到對應的內容。
接下來,再來一個稍微複雜的範例,讓大家看看如何使用GET方法傳遞路徑參數:
use axum::{
Router,
routing::get,
extract::Path, // 用於提取路徑參數
};
#[tokio::main]
async fn main() {
let app = Router::new()
// {id} 是動態參數,隨著輸入的內容改變
.route("/user/{id}", get(get_user))
.route("/post/{year}/{month}/{day}", get(get_post));
println!(" 可以瀏覽以下網址查看結果:");
println!(" http://127.0.0.1:3000/user/123");
println!(" http://127.0.0.1:3000/post/2025/09/23");
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
// 提取一個參數的Handler
async fn get_user(Path(id): Path<u32>) -> String {
format!("你正在查看用戶 #{} 的資料", id)
}
// 提取多個參數的Handler
async fn get_post(
Path((year, month, day)): Path<(u32, u32, u32)>
) -> String {
format!("查看 {}/{}/{} 的文章", year, month, day)
}
接著是一個查詢參數的範例:
use axum::{
Router,
routing::get,
extract::{Path, Query},
};
use serde::Deserialize; // 需要在 Cargo.toml 添加 serde = { version = "1.0", features = ["derive"] }
// 定義查詢參數結構
#[derive(Deserialize)]
struct Pagination {
page: Option<u32>,
limit: Option<u32>,
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/products", get(list_products))
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
// 查詢參數
async fn list_products(Query(params): Query<Pagination>) -> String {
let page = params.page.unwrap_or(1);
let limit = params.limit.unwrap_or(10);
format!(
"顯示產品列表\n頁數: {}\n每頁數量: {}",
page, limit
)
}
我們可以瀏覽 127.0.0.1:3000/products?page=1&limit=10 來測試這個程式,顯示的結果是
顯示產品列表
頁數: 1
每頁數量: 10
你可能會說這兩個範例幾乎是一樣的,但它們有點不同。
雖然兩者都是傳遞資料的方式,但它們在 URL 結構和使用場景上有明顯區別:
// 路徑參數:在 URL 路徑中
GET /user/123 // 123 是路徑參數
GET /post/2025/09/23 // 2025, 09, 23 都是路徑參數
// 查詢參數:在 ? 後面,用 & 分隔
GET /products?page=2&limit=10 // page=2 和 limit=10 是查詢參數
特性 | 路徑參數 (Path) | 查詢參數 (Query) |
---|---|---|
URL 格式 | /user/:id → /user/123 |
/products?page=1&limit=10 |
必要性 | 必需填入內容 | 可以省略 |
用途 | 識別特定資源 | 過濾、排序、分頁 |
範例:
use axum::{
Router,
routing::{get, post},
Json, // 用來處理 JSON
};
use serde::{Deserialize, Serialize};
// 定義請求和回應的資料結構
#[derive(Deserialize)] // 用來接收資料(反序列化)
struct User {
username: String,
email: String,
password: String,
}
#[derive(Serialize)] // 用來回傳資料(序列化)
struct UserResponse {
id: u32,
username: String,
message: String,
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/users", post(create_user));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn create_user(Json(payload): Json<User>) -> Json<UserResponse> {
// 在實際應用程式,會在資料庫執行操作,但我們還沒有資料庫,先用一個println代替
println!("收到新用戶註冊:{}", payload.username);
// 回傳 JSON 回應
Json(UserResponse {
id: 123, // 我們尚未連接資料庫,先假裝是資料庫產生的 id
username: payload.username,
message: format!("歡迎加入!你的信箱是 {}", payload.email),
})
}
我們可以用curl或postman來測試我們的API是否正常運作
curl -X POST http://127.0.0.1:3000/users -H "Content-Type: application/json" -d "{\"username\":\"user1\",\"email\":\"user1@aaa.com\",\"password\":\"12345678\"}"
我們應該可以得到以下的JSON回應
{"id":123,"username":"user1","message":"歡迎加入!你的信箱是 user1@aaa.com"}