Skip to content

Instantly share code, notes, and snippets.

@jiayun
Created March 4, 2026 15:06
Show Gist options
  • Select an option

  • Save jiayun/aff36dfeba415d7feaae797f225eced6 to your computer and use it in GitHub Desktop.

Select an option

Save jiayun/aff36dfeba415d7feaae797f225eced6 to your computer and use it in GitHub Desktop.
Ch24 Web API Server — 完整範例
// 執行:cargo run -p ch24_web_api_server,然後用 curl 測試
//
// 範例 curl 指令:
// curl http://localhost:3000/todos
// curl -X POST http://localhost:3000/todos -H "Content-Type: application/json" -d '{"title":"學 Rust"}'
// curl http://localhost:3000/todos/1
// curl -X DELETE http://localhost:3000/todos/1
use axum::{
Json, Router,
extract::{Path, State},
http::StatusCode,
response::IntoResponse,
routing::get,
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
// === 資料模型 ===
/// Todo 項目
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Todo {
id: u64,
title: String,
completed: bool,
}
/// 建立 Todo 的請求
#[derive(Debug, Deserialize)]
struct CreateTodo {
title: String,
}
// === 共享狀態 ===
/// 應用程式狀態,使用 Arc<Mutex<>> 實現執行緒安全的共享
#[derive(Clone)]
struct AppState {
todos: Arc<Mutex<Vec<Todo>>>,
next_id: Arc<Mutex<u64>>,
}
impl AppState {
fn new() -> Self {
Self {
todos: Arc::new(Mutex::new(Vec::new())),
next_id: Arc::new(Mutex::new(1)),
}
}
}
// === 處理函式(Handlers)===
/// GET /todos — 列出所有待辦事項
async fn list_todos(State(state): State<AppState>) -> Json<Vec<Todo>> {
let todos = state.todos.lock().expect("mutex poisoned");
Json(todos.clone())
}
/// POST /todos — 建立新的待辦事項
async fn create_todo(
State(state): State<AppState>,
Json(input): Json<CreateTodo>,
) -> impl IntoResponse {
let mut next_id = state.next_id.lock().expect("mutex poisoned");
let mut todos = state.todos.lock().expect("mutex poisoned");
let todo = Todo {
id: *next_id,
title: input.title,
completed: false,
};
*next_id += 1;
todos.push(todo.clone());
(StatusCode::CREATED, Json(todo))
}
/// GET /todos/:id — 取得單一待辦事項
async fn get_todo(
State(state): State<AppState>,
Path(id): Path<u64>,
) -> Result<Json<Todo>, StatusCode> {
let todos = state.todos.lock().expect("mutex poisoned");
todos
.iter()
.find(|t| t.id == id)
.cloned()
.map(Json)
.ok_or(StatusCode::NOT_FOUND)
}
/// DELETE /todos/:id — 刪除待辦事項
async fn delete_todo(
State(state): State<AppState>,
Path(id): Path<u64>,
) -> Result<impl IntoResponse, StatusCode> {
let mut todos = state.todos.lock().expect("mutex poisoned");
let len_before = todos.len();
todos.retain(|t| t.id != id);
if todos.len() == len_before {
Err(StatusCode::NOT_FOUND)
} else {
Ok(StatusCode::NO_CONTENT)
}
}
// === 路由設定 ===
fn create_router() -> Router {
let state = AppState::new();
Router::new()
.route("/todos", get(list_todos).post(create_todo))
.route("/todos/{id}", get(get_todo).delete(delete_todo))
.with_state(state)
}
// === 主程式 ===
#[tokio::main]
async fn main() {
let app = create_router();
println!("🚀 伺服器啟動於 http://localhost:3000");
println!(" GET /todos — 列出所有待辦事項");
println!(" POST /todos — 建立新待辦事項");
println!(" GET /todos/{{id}} — 取得單一待辦事項");
println!(" DELETE /todos/{{id}} — 刪除待辦事項");
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.expect("無法綁定連接埠 3000");
axum::serve(listener, app).await.expect("伺服器啟動失敗");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment