在上一篇文章中,我們學習了如何使用 Rocket 建立一個簡單的 Web 應用,並使用 Tera 模板引擎進行 HTML 渲染。這次,我們將繼續探討如何使用 Rocket 建立一個 RESTful API 並與 MongoDB 進行資料庫互動。這種設計在開發 Web 服務中特別常見,特別是需要儲存數據和進行 CRUD 操作時(Create、Read、Update、Delete,分別代表創建、讀取、更新和刪除)。
簡單來說,RESTful API 是一種基於 HTTP 協定的 API 設計風格。它通過 HTTP 請求來對資源進行操作,這些操作包括:
/users
來獲取使用者列表,GET /users/1
獲取 ID 為 1 的使用者。/users
來新增一名使用者。/users/1
修改使用者資料。/users/1
刪除該使用者。RESTful API 的無狀態特性要求每次請求都包含所有所需資訊,伺服器不會保留先前請求的上下文,這使得 API 具備高度可擴展性與靈活性。
首先我們要先在 MongoDB官網 建立一個雲端資料庫,實際操作步驟如下
mongoDB官網點選免費試用,然後就可以用google帳號登入了
接下來在此畫面點選建立一個新的組織,名字可以自定義
建立組織之後會看到這個畫面,我們可以接著點選 New Project
以建立專案
建立專案完畢後,在這邊點選 Create a cluster 當中的 +Create
按鈕,進入建立資料庫環節
這邊應該就很好選了,免費的 M0
方案有提供512MB的免費容量空間,做為輕量型開發綽綽有餘
下面這邊我們選擇 Google Cloud ,然後國家選擇台灣
接下來會得到一組 Username
跟 Password
,可以直接修改密碼,先記得複製記錄下來
使用者建立完畢之後,按下 Choose a connection method
連接方法點選第一個 Drivers
在語言選擇方面,選擇 Rust
的 2.1
這邊請選擇 Show Password
並且複製網址後備用,然後按下 Done
接下來在後台就可以看得到剛剛完成的Dataset了
如果有資料操作可以在這邊看得到資料內容,剛開始可能會看到載入預設資料集,可以刪除也可保留
接下來我們就可以嘗試透過 Rust 跟 mongoDB互動了
在這個範例中,我們會使用 Rocket 建立一個 RESTful API,並結合 MongoDB 資料庫來進行數據操作。我們將創建、查詢、更新和刪除用戶資料。
首先建立一個專案 rust-restful-api
:
cargo new rust-restful-api
cd rust-restful-api
Cargo.toml
接著,我們需要更新 Cargo.toml
文件以引入必要的套件:
[package]
name = "rust-restful-api"
version = "0.1.0"
edition = "2021"
[dependencies]
rocket = { version = "0.5.0-rc.1", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
mongodb = "2.7"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
rocket:Rust 中一個流行的 Web 框架,提供簡單和強大的 API 來構建 Web 應用。我們使用 rocket
來建立 HTTP 路由,處理 GET、POST、PUT 和 DELETE 請求。這裡啟用了 json
特性,允許我們處理 JSON 格式的數據,這對 RESTful API 非常重要。
serde 和 serde_json:這兩個庫用來將 Rust 的數據結構與 JSON 進行互相轉換。serde
是一個高效的序列化/反序列化框架,能夠自動將 Rust 結構體轉換為 JSON,或者將 JSON 數據轉換回 Rust 結構。serde_json
是專門用來處理 JSON 的 serde
擴展。
mongodb:MongoDB 的官方 Rust 驅動,讓我們可以與 MongoDB 資料庫互動。這個驅動提供了所有 CRUD 操作(增、查、改、刪)的接口,並且支援 BSON(Binary JSON)來處理 MongoDB 中常用的數據格式,例如 ObjectId
。
tokio:tokio
是 Rust 中最流行的異步運行時,用來支援並發和非阻塞操作。這是我們構建異步應用的核心。MongoDB 驅動和 Rocket 框架都是異步的,而 tokio
提供了異步執行所需的核心基礎設施。
futures:futures
庫是 Rust 中標準的異步操作工具包。它提供了多種異步操作的接口和工具,讓我們能夠在異步流和任務間進行轉換。我們使用 futures
的 TryStreamExt
來處理 MongoDB 查詢返回的異步流。
在 main.rs
中,我們將設置 MongoDB 的連接並創建一個 User
集合來儲存用戶數據。以下是完整範例程式碼,你可以在 main.rs 中使用這個範例來測試整個 RESTful API 的功能,並與 MongoDB 進行互動,但記得其中 mongoDB串接網址
要改成自己的,其中 需要包含密碼
:
use futures::stream::TryStreamExt; // 導入 TryStreamExt,提供 `try_collect` 方法以簡化處理 MongoDB 查詢結果
use mongodb::{bson::doc, bson::oid::ObjectId, options::ClientOptions, Client}; // 導入 MongoDB 客戶端、選項及 BSON 文檔的處理工具
use rocket::{delete, fs::FileServer, get, post, put, routes, serde::json::Json, State}; // 導入 Rocket 框架的路由、HTTP 請求方法及狀態管理
use serde::{Deserialize, Serialize}; // 導入 Serde 序列化及反序列化,讓結構體能夠轉換成 JSON
// 定義一個用戶結構,支援 JSON 的序列化和反序列化,並實現 Debug 和 Clone 特徵
#[derive(Debug, Deserialize, Serialize, Clone)]
struct User {
#[serde(skip_serializing_if = "Option::is_none")] // 如果 id 欄位是 None,序列化時會跳過該欄位
id: Option<String>, // 用戶的 ID,可能不存在(可選)
name: String, // 用戶的名字,為必填欄位
}
// Rocket 主程式入口,使用 async 函數以支援異步操作
#[rocket::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 設置 MongoDB 連接選項,使用 MongoDB Atlas 提供的 URL 進行連接
let client_options = ClientOptions::parse("mongodb+srv://masoufo0310:2ioxaagu@database.541vj.mongodb.net/?retryWrites=true&w=majority&appName=Database").await?;
let client = Client::with_options(client_options)?; // 建立 MongoDB 客戶端連接
let _db = client.database("testdb"); // 取得指定的資料庫 testdb
// 構建 Rocket 應用,並管理 MongoDB 客戶端狀態
rocket::build()
.manage(client) // 傳遞 MongoDB 客戶端到 Rocket 管理狀態
.mount(
// 註冊路由,將所有 CRUD 操作綁定到應用上
"/",
routes![create_user, get_users, update_user, delete_user], // 綁定路由,處理用戶的新增、查詢、更新、刪除
)
.mount("/", FileServer::from("../frontend/build")) // 讓 Rocket 提供 React build 目錄中的靜態資源
.ignite() // 啟動 Rocket 應用,初始化伺服器
.await?
.launch() // 啟動伺服器,並開始監聽請求
.await?;
Ok(()) // 正常退出時返回 OK
}
// POST 請求,用來新增用戶
#[post("/users", data = "<user>")]
async fn create_user(client: &State<Client>, user: Json<User>) -> Json<User> {
let collection = client.database("testdb").collection::<User>("users"); // 連接到 MongoDB 的 testdb 資料庫,並選擇 users 集合
let new_user = User {
id: None, // 新增用戶時,ID 設為 None,由 MongoDB 自動生成
name: user.name.clone(), // 複製請求中的用戶名稱
};
collection.insert_one(new_user.clone(), None).await.unwrap(); // 將新用戶資料插入資料庫,忽略錯誤處理
Json(new_user) // 回傳新增的用戶資料作為 JSON
}
// GET 請求,用來獲取所有用戶資料
#[get("/users")]
async fn get_users(client: &State<Client>) -> Json<Vec<User>> {
let collection = client
.database("testdb")
.collection::<mongodb::bson::Document>("users"); // 查詢集合並指定返回 Document 類型
let mut cursor = collection.find(None, None).await.unwrap(); // 從集合中查詢所有用戶,獲取 MongoDB 游標
let mut users: Vec<User> = Vec::new(); // 定義一個空的 Vec 來存放用戶
while let Some(doc) = cursor.try_next().await.unwrap() {
let id = doc.get_object_id("_id").unwrap().to_hex(); // 從 BSON 文檔中提取 ObjectId 並轉換為字串
let name = doc.get_str("name").unwrap().to_string(); // 從 BSON 文檔中提取用戶的名字
let user = User { id: Some(id), name }; // 構建用戶對象
users.push(user); // 將用戶加入到用戶列表
}
Json(users) // 回傳所有用戶的 JSON 數據
}
// PUT 請求,用來更新指定 ID 的用戶資料
#[put("/users/<id>", data = "<user>")]
async fn update_user(client: &State<Client>, id: String, user: Json<User>) -> Json<User> {
let collection = client.database("testdb").collection::<User>("users");
// 將 id 轉換為 ObjectId
let object_id = match ObjectId::parse_str(&id) {
Ok(oid) => oid,
Err(_) => {
return Json(User {
id: Some(id),
name: "Invalid ID format".to_string(),
})
}
};
let filter = doc! { "_id": object_id }; // 根據 ObjectId 進行過濾
let update = doc! { "$set": { "name": &user.name } }; // 設置更新條件
collection.update_one(filter, update, None).await.unwrap();
Json(User {
id: Some(id), // 更新後的用戶 ID 設為指定的 ID
name: user.name.clone(), // 返回更新後的用戶名字
})
}
// DELETE 請求,用來刪除指定 ID 的用戶
#[delete("/users/<id>")]
async fn delete_user(client: &State<Client>, id: String) -> String {
let collection = client.database("testdb").collection::<User>("users"); // 連接到 MongoDB 的 testdb 資料庫,並選擇 users 集合
// 將傳入的 id 轉換為 ObjectId,如果失敗則回傳錯誤訊息
let object_id = match ObjectId::parse_str(&id) {
Ok(oid) => oid,
Err(_) => return format!("Invalid ID format: {}", id),
};
let filter = doc! { "_id": object_id }; // 使用 BSON 格式,根據 ObjectId 進行過濾
// 執行刪除操作,忽略錯誤處理
match collection.delete_one(filter, None).await {
Ok(result) => {
if result.deleted_count == 1 {
format!("User with id {} deleted", id) // 回傳刪除成功訊息
} else {
format!("No user found with id {}", id) // 如果沒有找到用戶,回傳未找到訊息
}
}
Err(_) => format!("Failed to delete user with id {}", id), // 刪除失敗時回傳錯誤訊息
}
}
我們定義了四組 API 路由來處理用戶數據,包括新增、查詢、更新和刪除用戶。
#[post("/users", data = "<user>")]
async fn create_user(client: &State<Client>, user: Json<User>) -> Json<User> {
let collection = client.database("testdb").collection::<User>("users");
let new_user = User { id: None, name: user.name.clone() };
collection.insert_one(new_user.clone(), None).await.unwrap();
Json(new_user)
}
這個部分處理新增用戶的操作。當接收到一個 POST 請求時,API 會將請求中的 JSON 數據解析為 User 結構,然後將該用戶的數據插入 MongoDB 中的 users
集合。插入完成後,返回該用戶的數據給前端。
查詢所有用戶的 API 路由:
#[get("/users")]
async fn get_users(client: &State<Client>) -> Json<Vec<User>> {
let collection = client
.database("testdb")
.collection::<mongodb::bson::Document>("users"); // 查詢集合並指定返回 Document 類型
let mut cursor = collection.find(None, None).await.unwrap(); // 從集合中查詢所有用戶,獲取 MongoDB 游標
let mut users: Vec<User> = Vec::new(); // 定義一個空的 Vec 來存放用戶
while let Some(doc) = cursor.try_next().await.unwrap() {
let id = doc.get_object_id("_id").unwrap().to_hex(); // 從 BSON 文檔中提取 ObjectId 並轉換為字串
let name = doc.get_str("name").unwrap().to_string(); // 從 BSON 文檔中提取用戶的名字
let user = User { id: Some(id), name }; // 構建用戶對象
users.push(user); // 將用戶加入到用戶列表
}
Json(users) // 回傳所有用戶的 JSON 數據
}
這段代碼處理取得所有用戶的操作。當發送 GET 請求時,API 從 users 集合中查詢所有用戶,將其轉換為 Vec,然後以 JSON 格式返回用戶數據。
更新指定用戶的 API 路由:
#[put("/users/<id>", data = "<user>")]
async fn update_user(client: &State<Client>, id: String, user: Json<User>) -> Json<User> {
let collection = client.database("testdb").collection::<User>("users");
let filter = doc! { "_id": id.clone() };
let update = doc! { "$set": { "name": &user.name } };
collection.update_one(filter, update, None).await.unwrap();
Json(User { id: Some(id), name: user.name.clone() })
}
這個部分處理用戶更新。PUT 請求包含了用戶的 ID 及要更新的資料。API 根據提供的用戶 ID 在資料庫中找到該用戶,然後更新他的 name 欄位,並返回更新後的資料。
刪除指定用戶的 API 路由:
#[delete("/users/<id>")]
async fn delete_user(client: &State<Client>, id: String) -> String {
let collection = client.database("testdb").collection::<User>("users");
let filter = doc! { "_id": id.clone() };
collection.delete_one(filter, None).await.unwrap();
format!("User with id {} deleted", id)
}
這段程式碼處理刪除用戶。當接收到一個 DELETE 請求時,API 根據請求中的用戶 ID,將相應的用戶從 MongoDB 中刪除,並返回刪除成功的訊息。
可以使用 Invoke-RestMethod
、 curl
或 Postman 來測試 API。
Windows系統(Powershell)
Invoke-RestMethod -Uri http://localhost:8000/users -Method POST -Body '{"name": "Alice"}' -ContentType 'application/json'
Linux系統
curl -X POST -H "Content-Type: application/json" -d '{"name": "Alice"}' http://localhost:8000/users
這個指令會向 API 發送一個 POST 請求,新增一名名為 Alice 的用戶,發送成功之後會得到以下結果:
建立完使用者之後,我們可以開啟瀏覽器輸入 http://localhost:8000/users
這個 URL 會向 API 發送一個 GET request,並列出所有的用戶數據。
好的,以下是如何測試 PUT 和 DELETE 請求的部分。
我們使用 PUT
請求來更新指定用戶的資料。假設我們已經新增了一名名為 Alice 的用戶,現在想將她的名字更新為 Alice Doe。
Windows 系統 (Powershell):
Invoke-RestMethod -Uri http://localhost:8000/users/<user_id> -Method PUT -Body '{"name": "Alice Doe"}' -ContentType 'application/json'
Linux 系統:
curl -X PUT -H "Content-Type: application/json" -d '{"name": "Alice Doe"}' http://localhost:8000/users/<user_id>
請將 <user_id>
替換為你要更新的用戶的實際 ID。PUT
請求會將指定 ID 的用戶名字更新為 "Alice Doe"。成功後,你會收到更新後的用戶資料作為回應。
例如,如果 ID 為 614b2d39f1a3e8e4c1234567
,可以這樣發送請求:
Windows 系統 (Powershell):
Invoke-RestMethod -Uri http://localhost:8000/users/614b2d39f1a3e8e4c1234567 -Method PUT -Body '{"name": "Alice Doe"}' -ContentType 'application/json'
Linux 系統:
curl -X PUT -H "Content-Type: application/json" -d '{"name": "Alice Doe"}' http://localhost:8000/users/614b2d39f1a3e8e4c1234567
執行成功後,將會得到如下回應:
DELETE
請求用於刪除指定的用戶,假設我們想刪除 ID 為 614b2d39f1a3e8e4c1234567
的用戶。
Windows 系統 (Powershell):
Invoke-RestMethod -Uri http://localhost:8000/users/<user_id> -Method DELETE
Linux 系統:
curl -X DELETE http://localhost:8000/users/<user_id>
同樣,將 <user_id>
替換為你要刪除的用戶的實際 ID。例如,刪除 ID 為 614b2d39f1a3e8e4c1234567
的用戶:
Windows 系統 (Powershell):
Invoke-RestMethod -Uri http://localhost:8000/users/614b2d39f1a3e8e4c1234567 -Method DELETE
Linux 系統:
curl -X DELETE http://localhost:8000/users/614b2d39f1a3e8e4c1234567
成功後,會返回一個確認刪除的訊息,例如:
這樣就完成了 CRUD 操作的所有部分,通過這些指令,你可以使用 POST
、GET
、PUT
和 DELETE
操作來測試你建立的 RESTful API 與 MongoDB 的互動。在MongoDB後台,我們也可以看得到新增的資料。
在這篇文章中,我們學習了如何使用 Rocket 和 MongoDB 建立 RESTful API,並與 MongoDB 進行互動。我們能夠輕鬆地處理資料庫中的數據,並提供一個 API 給外部應用使用。希望這篇文章對你在 Rust Web 開發過程中有所幫助,接下來,我們就要更進一步將 React 作為前端, Rust 作為後端的部分整合,完成一個 React + Rust 的 Web service 實作囉!