用 Rust 構建 API 系列教程:第二部分
關注「Rust 編程指北」,一起學習 Rust,給未來投資
大家好,我是胖蟹哥。
今天繼續使用 Rust 構建 API,第二部分。
這部分我將解釋如何將 API 連接到 MongoDB。我將創建兩個端點,一個用於在數據庫中添加數據,另一個用於從數據庫中檢索數據。
我將把所有代碼放在同一個文件 (src/main.rs
) 中。當然,在實際情況中,你應該將代碼拆分爲多個文件 / 目錄,並將控制器和服務之間的邏輯分離。
爲了能夠跟着本文實際動手,你需要安裝 Rust 和 MongoDB,同時啓動 MongoDB。如果你有使用 Docker,以下方式可能是最簡單的:
docker run -d -p 27017:27017 --name mongo mongo
搭建環境
我們需要創建一個 .env
文件,安全地存儲 MongoDB 連接字符串。如果你使用 Git,請確保添加 .env
到 .gitignore
中。永遠不要把 .env
文件提交了。
打開本系列第 1 部分的項目並創建 .env
文件,並添加如下內容:
MONGODB_URI=mongodb://localhost:27017
根據運行 MongoDB 的方式,確保將連接字符串替換爲正確的連接字符串。
最好的做法是有一個 .env.example
文件(這個文件應該被提交到代碼倉庫) ,這樣當某人第一次拉取項目時,他們就有一個關於如何設置 .env
文件的模板。這也是目前的最佳實踐!
創建一個 .env.example
文件,添加如下內容:
MONGODB_URI= ** MongoDB URI (e.g. mongodb://localhost:27017)
添加新的依賴項
我們需要添加 2 個新的依賴項:dotenv
和 mongodb
。
dotenv
是一個 crate,允許我們使用 .env
文件,它將文件中的變量添加到環境變量中。
mongodb
是官方的 MongoDB Rust 驅動程序。它允許我們與數據庫交互。
更新 Cargo.toml
文件:
[dependencies]
tide = "0.16"
async-std = { version = "1", features = ["attributes"] }
serde = { version = "1", features = ["derive"] }
dotenv = "0.15"
mongodb = { version = "1", features = ["async-std-runtime"], default-features = false }
因爲我們使用了 async-std
運行時,所以我們需要禁用 mongodb
crate 的缺省特性。因爲默認情況下它使用 tokio
。然後我們需要添加 async-std-runtime
特性來使用 async-std
運行時。
開始編碼
我們需要對代碼進行一些修改:
use async_std::stream::StreamExt;
use dotenv::dotenv;
use mongodb::bson::doc;
use serde::{Deserialize, Serialize};
use std::env;
use tide::{Body, Request, Response, StatusCode};
這些都是我們將使用的所有模塊項。
還記得上次我們設置了一個空的 Tide State
嗎?這次我們將使用 State
將數據庫連接傳遞給控制器。我們需要像這樣更新 State
結構
#[derive(Clone)]
struct State {
db: mongodb::Database,
}
現在我們需要更新 main
函數。必須創建數據庫連接,並在創建 Tide 應用程序之前添加它的狀態。
下面是更新後的 main
函數:
#[async_std::main]
async fn main() -> tide::Result<()> {
// Use the dotenv crate to read the .env file and add the environment variables
dotenv().ok();
// Create the MongoDB client options with the connection string from the environment variables
let mongodb_client_options =
mongodb::options::ClientOptions::parse(&env::var("MONGODB_URI").unwrap()).await?;
// Instantiate the MongoDB client
let mongodb_client = mongodb::Client::with_options(mongodb_client_options)?;
// Get a handle to the "rust-api-example" database
let db = mongodb_client.database("rust-api-example");
// Create the Tide state with the database connection
let state = State { db };
let mut app = tide::with_state(state);
app.at("/hello").get(hello);
app.listen("127.0.0.1:8080").await?;
return Ok(());
}
我在代碼中添加了註釋,以幫助你理解發生了什麼。
首先調用了 dotenv().ok()
方法從 .env
文件讀取並添加到環境變量。然後創建了一個到數據庫的連接。最後,我把這個連接傳遞給了 Tide State
,這樣我們的控制器就可以訪問它了。
現在我們將創建兩個控制器,一個用於在數據庫中創建文檔,另一個用於檢索這些文檔。
下面是第一個控制器的代碼:
#[derive(Debug, Serialize, Deserialize)]
// The request's body structure
pub struct Item {
pub name: String,
pub price: f32,
}
async fn post_item(mut req: Request<State>) -> tide::Result {
// Read the request's body and transform it into a struct
let item = req.body_json::<Item>().await?;
// Recover the database connection handle from the Tide state
let db = &req.state().db;
// Get a handle to the "items" collection
let items_collection = db.collection_with_type::<Item>("items");
// Insert a new Item in the "items" collection using values
// from the request's body
items_collection
.insert_one(
Item {
name: item.name,
price: item.price,
},
None,
)
.await?;
// Return 200 if everything went fine
return Ok(Response::new(StatusCode::Ok));
}
首先,我定義了一個 Item
結構體,它表示請求的主體。主體應該是一個 JSON 對象,其屬性 name
的類型爲 string
,屬性 price
的類型爲 number
。
在函數內部,我首先嚐試讀取請求體。我沒有做任何驗證。實際項目中,不要像我一樣,應該總是在使用請求體之前驗證它。
我從 Tide State
恢復數據庫連接。然後我得到一個 items
集合的句柄。
最後,我嘗試使用請求體的值在集合中插入一個新文檔(Mongo 的 Document)。
如果一切順利的話,會返回 HTTP 狀態碼 200 的響應。
現在,讓我們創建第二個控制器來從 items
集合中檢索文檔,下面是代碼:
async fn get_items(req: Request<State>) -> tide::Result<tide::Body> {
// Recover the database connection handle from the Tide state
let db = &req.state().db;
// Get a handle to the "items" collection
let items_collection = db.collection_with_type::<Item>("items");
// Find all the documents from the "items" collection
let mut cursor = items_collection.find(None, None).await?;
// Create a new empty Vector of Item
let mut data = Vec::<Item>::new();
// Loop through the results of the find query
while let Some(result) = cursor.next().await {
// If the result is ok, add the Item in the Vector
if let Ok(item) = result {
data.push(item);
}
}
// Send the response with the list of items
return Body::from_json(&data);
}
在函數內部,我從 Tide State
檢索到數據庫連接的句柄,然後獲得 items
集合的句柄。
我使用 find
函數檢索 items
集合中的所有文檔。然後我創建一個新的空的 Vector Item。
我循環 find
查詢結果,並在 Vector 中插入每個 item
。
最後,我將響應與主體中的 items 列表一起發送。
控制器已經準備好了,但是我們仍然需要將它們添加到 main
函數中。在 main
函數的 hello
路由之後添加以下內容:
app.at("/items").get(get_items);
app.at("/items").post(post_item);
好了,讓我們測試一下,用下面的命令啓動服務器:
cargo run
你現在應該能夠發送一個 POST
請求給 /items
(http://localhost:8080/items
)。下面是 Body 的一個示例(確保將 Content-Type
頭設置爲 application/json
)。
{
"name": "coffee",
"price": 2.5
}
你應該得到一個狀態碼爲 200 的空響應。
然後嘗試檢索你剛剛創建的文檔,通過發送一個 GET
請求給 /items
( http://localhost:8080/items
) 獲得。
你應該接收到一個數組,其中一個條目就是我們剛剛創建的項。
[
{
"name": "coffee",
"price": 2.5
}
]
結尾
這就是該系列的第二部分,希望它對你有所幫助。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/wWoxMFmy3bwYXEhiIYtVdQ