首先建立專案
cargo new axum_restful_api
Cargo.toml(專案依賴)
[package]
name = "axum_restful_api"
version = "0.1.0"
edition = "2024"
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tracing = "0.1"
tracing-subscriber = "0.3"
parking_lot = "0.12"
main.rs(使用 Arc + RwLock 與 AtomicU64 產生 id)
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::sync::RwLock;
use axum::{
extract::{Path, Extension},
http::StatusCode,
response::IntoResponse,
Json, Router, routing::{get, post, put, delete},
};
use serde::{Deserialize, Serialize};
use tracing_subscriber;
#[derive(Clone, Serialize, Deserialize)]
struct Task {
id: u64,
title: String,
description: Option<String>,
completed: bool,
}
#[derive(Deserialize)]
struct CreateTask {
title: String,
description: Option<String>,
}
#[derive(Deserialize)]
struct UpdateTask {
title: Option<String>,
description: Option<String>,
completed: Option<bool>,
}
// 共享狀態型別:HashMap<id, Task>
type Db = Arc<RwLock<HashMap<u64, Task>>>;
type IdCounter = Arc<AtomicU64>;
#[tokio::main]
async fn main() {
// 初始化 tracing 用於除錯 / 日誌
tracing_subscriber::fmt::init();
// 建立空的記憶體資料庫
let db: Db = Arc::new(RwLock::new(HashMap::new()));
let id_counter = Arc::new(AtomicU64::new(1));
// 建立路由並把共享狀態透過 Extension 注入 handler
let app = Router::new()
.route("/tasks", get(list_tasks).post(create_task))
.route("/tasks/{id}", get(get_task).put(update_task).delete(delete_task))
.layer(Extension(db.clone()))
.layer(Extension(id_counter.clone()));
// 啟動服務(監聽 3000)
tracing::info!("Starting server at http://127.0.0.1:3000");
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app)
.await
.unwrap();
}
// Handler:列出所有任務
async fn list_tasks(Extension(db): Extension<Db>) -> impl IntoResponse {
let map = db.read().await;
let tasks: Vec<Task> = map.values().cloned().collect();
(StatusCode::OK, Json(tasks))
}
// Handler:取得單一任務
async fn get_task(Path(id): Path<u64>, Extension(db): Extension<Db>) -> impl IntoResponse {
let map = db.read().await;
if let Some(task) = map.get(&id) {
(StatusCode::OK, Json(task.clone())).into_response()
} else {
(StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "task not found"}))).into_response()
}
}
// Handler:建立任務
async fn create_task(
Extension(db): Extension<Db>,
Extension(id_counter): Extension<IdCounter>,
Json(payload): Json<CreateTask>,
) -> impl IntoResponse {
let id = id_counter.fetch_add(1, Ordering::Relaxed);
let task = Task {
id,
title: payload.title,
description: payload.description,
completed: false,
};
let mut map = db.write().await;
map.insert(id, task.clone());
(StatusCode::CREATED, Json(task))
}
// Handler:更新任務(部分更新)
async fn update_task(
Path(id): Path<u64>,
Extension(db): Extension<Db>,
Json(payload): Json<UpdateTask>,
) -> impl IntoResponse {
let mut map = db.write().await;
if let Some(task) = map.get_mut(&id) {
if let Some(title) = payload.title { task.title = title; }
if let Some(desc) = payload.description { task.description = Some(desc); }
if let Some(comp) = payload.completed { task.completed = comp; }
return (StatusCode::OK, Json(task.clone())).into_response();
}
(StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "task not found"}))).into_response()
}
// Handler:刪除任務
async fn delete_task(Path(id): Path<u64>, Extension(db): Extension<Db>) -> impl IntoResponse {
let mut map = db.write().await;
if map.remove(&id).is_some() {
(StatusCode::NO_CONTENT, Json(serde_json::json!({}))).into_response()
} else {
(StatusCode::NOT_FOUND, Json(serde_json::json!({"error": "task not found"}))).into_response()
}
}
建立任務(POST /tasks)
POST http://127.0.0.1:3000/tasks
{
"title": "買牛奶",
"description": "逛超市時順便買"
}
回應 201 Created,body 為建立後的 task(含 id)
列出所有任務(GET /tasks)
GET http://127.0.0.1:3000/tasks
回應 200 OK,body 為任務陣列
取得單一任務(GET /tasks/{id})
GET http://127.0.0.1:3000/tasks/1
若存在回 200 和該 task,否則 404
更新任務(PUT /tasks/{id}),可以只帶要更新的欄位
PUT http://127.0.0.1:3000/tasks/1
{
"completed": true
}
回應 200 OK 或 404
刪除任務(DELETE /tasks/:id)
DELETE http://127.0.0.1:3000/tasks/1
若成功回 204 No Content,否則 404