所有文章 > 日积月累 > 用Rust和MongoDB构建一个REST API - Actix web版本
用Rust和MongoDB构建一个REST API - Actix web版本

用Rust和MongoDB构建一个REST API - Actix web版本

REST API 已成为将数据从一个源传输到另一个源的实际手段,并提供了一组指导和架构模式,用于设计和开发Web服务。

这篇文章将讨论使用Actix Web框架和MongoDB使用 Rust 构建用户管理应用程序。在本教程的最后,我们将学习如何构建 Rust 应用程序、构建 REST API 并使用 MongoDB 保存数据。

Actix web 是一个用 Rust 编写的 HTTP Web 框架,提供性能和生产力支持。 Actix Web 附带了类型安全、可重用性、日志记录、静态文件服务等功能,开发人员可以利用这些功能来构建可扩展的应用程序。

MongoDB 是一种文档型数据库管理系统,可以作为关系数据库的替代方案。它支持处理大量分布式数据,并提供无缝存储和检索信息的选项。

先决条件

要完全掌握本教程中介绍的概念,需要具有 Rust 经验。虽然使用MongoDB的经验不是必需的,但如果有的话会更好。

我们还需要以下内容:

  • 用于托管数据库的MongoDB帐户。注册完全免费
  • Postman或任何 API 测试应用程序

让我们来编码

入门

首先,我们需要导航到所需的目录并在终端中运行以下命令

cargo new actix-mongo-api && cd actix-mongo-api

此命令创建一个名为 actix-mongo-api的 Rust 项目 并导航到项目目录。

接下来,我们通过修改Cargo.toml文件的部分来安装所需的依赖项,如下所示:

//other code section goes here

[dependencies]
actix-web = "4"
serde = "1.0.136"
dotenv = "0.15.0"
futures = "0.3"

[dependencies.mongodb]
version = "2.2.0"
default-features = false
features = ["async-std-runtime"]

actix-web = "4"是一个基于 Rust 的框架,用于构建 Web 应用程序。

serde = "1.0.136"是一个用于序列化和反序列化 Rust 数据结构的框架。例如,将 Rust 结构转换为 JSON。

dotenv = "0.15.0"是一个用于管理环境变量的库。

futures = "0.3"是一个用 Rust 进行异步编程的库

[dependencies.mongodb]是用于连接 MongoDB 的驱动程序。它还指定所需的版本和功能类型(异步 API)。

我们需要运行以下命令来安装依赖项:

 cargo build

应用程序入口点

安装项目依赖项之后,请修改位于src文件夹中的main.rs文件,将其内容更新为以下所示:

use actix_web::{get, App, HttpResponse, HttpServer, Responder};

#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().json("Hello from rust and mongoDB")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(hello))
.bind(("localhost", 8080))?
.run()
.await
}

上面的代码片段执行以下操作:

  • 导入所需的依赖项
  • 编写一个名为hello的处理程序,该程序利用Rust宏来指定HTTP方法以及路由路径/,并且返回JSON格式的数据,内容为“Hello from rust and mongoDB”。
  • 使用#[actix_web::main]宏在Actix运行时内异步运行一个main函数,该main函数执行以下操作:
    • 我们利用结构体创建了一个新的HttpServer服务器,该服务器通过闭包与一个App实例相结合,用于处理传入的请求。App负责注册各种处理程序,例如hello处理程序。在这个架构中,HttpServer扮演着应用程序核心支柱的角色,它处理请求、管理允许的最大连接数、实施分层安全性等关键任务;而App则专注于处理应用程序逻辑,包括请求处理程序、中间件、路由等。
    • 将服务器配置为异步运行并处理localhost:8080 上的 HTTP 请求。

接下来,我们可以通过在终端中运行以下命令来测试我们的应用程序。

cargo run
测试应用程序

Rust 中的模块系统

在Rust中,模块是一种机制,用于将代码拆分为可重用的组件,并管理这些组件之间的可见性。模块有助于我们维护项目,使其具有良好的结构。

为此,我们需要导航到该src文件夹​​并创建apimodelsrepository文件夹以及相应的mod.rs 文件来管理可见性。

更新了项目文件夹结构

api用于模块化 API 处理程序。

models用于模块化数据逻辑。

repository用于模块化数据库逻辑。

添加对模块的引用
要使用模块中的代码,我们需要将它们声明为模块并将它们导入到main.rs文件中。

//add the modules
mod api;
mod models;
mod repository;

use actix_web::{get, App, HttpResponse, HttpServer, Responder};

// the remaining part of our code goes here

设置 MongoDB

完成后,我们需要登录或注册我们的MongoDB帐户。单击项目下拉菜单,然后单击“新建项目”按钮。

新项目

输入rust-api为项目名称,单击Next,然后单击Create Project..

输入项目名称
创建项目

单击构建数据库

选择共享作为数据库类型。

共享内容以红色突出显示

单击“创建”以设置集群。这可能需要一些时间来设置。

创建集群

接下来,我们需要创建一个用户来从外部访问数据库,输入用户名密码,然后单击创建用户。我们还需要点击“添加我当前的IP地址”按钮,以便添加IP地址从而安全地连接到数据库。之后,点击“完成并关闭”以保存所做的更改。

创建用户
添加IP

保存更改后,我们应该看到数据库部署屏幕,如下所示:

数据库屏幕

将我们的应用程序连接到 MongoDB

完成配置后,我们需要将应用程序与创建的数据库连接起来。为此,请单击“连接”按钮

连接到数据库

单击“连接您的应用程序”,将“驱动程序”更改为“版本”,如下所示。然后单击复制图标复制连接字符串。

连接应用程序
复制连接字符串

设置环境变量
接下来,我们需要使用之前创建的用户密码来修改复制的连接字符串,并同时更改数据库的名称。为了完成这个任务,首先,我们需要在项目的根目录下创建一个名为.env的文件,并将复制的代码片段粘贴到这个文件中。

MONGOURI=mongodb+srv://<YOUR USERNAME HERE>:<YOUR PASSWORD HERE>@cluster0.e5akf.mongodb.net/myFirstDatabese?retryWrites=true&w=majority

下面是正确填充的连接字符串的示例:

MONGOURI=mongodb+srv://malomz:malomzPassword@cluster0.e5akf.mongodb.net/golangDB?retryWrites=true&w=majority

创建 REST API

设置完成后,我们需要创建一个模型来表示我们的应用程序数据。为此,我们需要导航到该models文件夹​​,并在此文件夹中创建一个user_model.rs文件并添加以下代码片段:

use mongodb::bson::oid::ObjectId;
use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct User {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<ObjectId>,
pub name: String,
pub location: String,
pub title: String,
}

上面的代码片段执行以下操作:

  • 导入所需的依赖项
  • 使用derive宏生成对格式化输出、序列化和反序列化数据结构的实现支持。
  • 创建 User具有所需属性的结构。我们还向属性添加了字段属性id,以重命名并忽略该字段(如果该字段为空)。

PS修饰符pub使结构及其属性公开,并且可以从其他文件/模块访问。

接下来,我们需要将user_model.rs文件注册为models模块的一部分。为此,请打开位于models文件夹中的mod.rs文件,并添加以下代码片段:

pub mod user_model;

创建用户端点
模型完全设置并可供使用后,我们现在可以创建数据库逻辑来创建用户。为此,首先,我们需要导航到该repository文件夹​​,并在此文件夹中创建一个mongodb_repo.rs文件并添加以下代码片段:

use std::env;
extern crate dotenv;
use dotenv::dotenv;

use mongodb::{
bson::{extjson::de::Error},
results::{ InsertOneResult},
Client, Collection,
};
use crate::models::user_model::User;

pub struct MongoRepo {
col: Collection<User>,
}

impl MongoRepo {
pub async fn init() -> Self {
dotenv().ok();
let uri = match env::var("MONGOURI") {
Ok(v) => v.to_string(),
Err(_) => format!("Error loading env variable"),
};
let client = Client::with_uri_str(uri).unwrap();
let db = client.database("rustDB");
let col: Collection<User> = db.collection("User");
MongoRepo { col }
}

pub async fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
let new_doc = User {
id: None,
name: new_user.name,
location: new_user.location,
title: new_user.title,
};
let user = self
.col
.insert_one(new_doc, None)
.await
.ok()
.expect("Error creating user");
Ok(user)
}
}

上面的代码片段执行以下操作:

  • 导入所需的依赖项
  • 创建一个MongoRepo带有col字段的结构体来访问 MongoDB 集合
  • MongoRepo创建一个向结构体添加方法的实现块
  • 我们需要在MongoRepo结构体的实现块中添加一个方法,这个方法的作用是加载环境变量、建立到数据库的连接,并最终返回MongoRepo的一个实例,我们可以将这个方法命名为init
  • 添加一个create_user方法,该方法接受selfnew_user作为参数并返回创建的用户或错误。在该方法内部,我们首先利用User结构体创建了一个新的文档。接着,我们通过self引用来访问MongoRepo结构体中的insert_one函数(该函数作用于某个集合),以便创建新的用户。在处理过程中,我们还对可能出现的错误进行了处理。最后,方法返回了所创建的用户信息。

接下来,我们必须将该mongodb_repo.rs文件注册为repository模块的一部分。为了完成这个任务,请打开位于repository文件夹中的mod.rs文件,并向其中添加以下代码片段:

pub mod mongodb_repos;

其次,我们需要创建一个处理程序,使用 create_user中的方法来创建用户。为此,我们需要导航到该api文件夹​​,并在此文件夹中创建一个user_api.rs文件并添加以下代码片段:

use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use actix_web::{
post,
web::{Data, Json},
HttpResponse,
};

#[post("/user")]
pub async fn create_user(db: Data<MongoRepo>, new_user: Json<User>) -> HttpResponse {
let data = User {
id: None,
name: new_user.name.to_owned(),
location: new_user.location.to_owned(),
title: new_user.title.to_owned(),
};
let user_detail = db.create_user(data).await;
match user_detail {
Ok(user) => HttpResponse::Ok().json(user),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}

上面的代码片段执行以下操作:

  • 导入所需的依赖项
  • 使用路由宏指定HTTP方法和对应的路由
  • 创建一个create_user处理程序,该处理程序接受dbMongoRepo的类型和 一个new_user作为参数。在处理程序内部,我们创建了一个data用于创建用户的变量,我们应该使用该方法将数据插入到数据库的db.create_user中,并根据插入操作是否成功或者是否发生错误,返回相应的响应。

PS: 定义参数时使用的 DataJson和结构分别用于管理跨路由共享的应用程序状态和从请求负载中提取 JSON 数据。

最后,我们需要修改应用程序入口点以包含create_user处理程序。为此,我们需要导航到该main.rs文件并对其进行修改,如下所示:

mod api;
mod models;
mod repository;

//modify imports below
use actix_web::{web::Data, App, HttpServer};
use api::user_api::{create_user};
use repository::mongodb_repo::MongoRepo;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
let db = MongoRepo::init().await;
let db_data = Data::new(db);
HttpServer::new(move || {
App::new()
.app_data(db_data.clone())
.service(create_user)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}

上面的代码片段执行以下操作:

  • 导入所需的依赖项
  • 创建一个db变量以通过调用该方法建立与 MongoDB 的连接init(),并将其添加到该Data结构的新实例中,以便数据库状态可以在整个应用程序范围内可用。
  • 使用app_dataservice 函数将应用程序数据和处理程序添加到App实例。

PS:关键字move用于闭包中,它的作用是赋予闭包对MongoDB配置的所有权。

获取用户端点
要获取用户的详细信息,我们必须首先通过get_user向实现块mongodb_repo.rs添加方法来修改文件。

use std::env;
extern crate dotenv;
use dotenv::dotenv;

use mongodb::{
bson::{extjson::de::Error, oid::ObjectId, doc}, //modify here
results::{ InsertOneResult},
Client, Collection,
};
use crate::models::user_model::User;

pub struct MongoRepo {
col: Collection<User>,
}

impl MongoRepo {
pub async fn init() -> Self {
//init code goes here
}

pub async fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
//create_user code goes here
}

pub async fn get_user(&self, id: &String) -> Result<User, Error> {
let obj_id = ObjectId::parse_str(id).unwrap();
let filter = doc! {"_id": obj_id};
let user_detail = self
.col
.find_one(filter, None)
.await
.ok()
.expect("Error getting user's detail");
Ok(user_detail.unwrap())
}
}

上面的代码片段执行以下操作:

  • 为了包含oid::ObjectIddoc,我们需要对依赖项进行修改。
  • 添加一个get_user方法,该方法接受selfid作为参数并返回用户详细信息或错误。在方法内部,我们将 id转换为 ObjectId 并将其用作过滤器来获取匹配的文档。然后,我们使用self引用该MongoRepo结构体来访问find_one集合中的函数,以获取用户的详细信息并处理错误。最后我们返回了创建的用户信息。

其次,我们需要user_api.rs通过创建一个处理程序来进行修改,该处理程序使用 get_user中的方法来repository获取用户。

use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use actix_web::{
post, get, //modify here
web::{Data, Json, Path}, //modify here
HttpResponse,
};

#[post("/user")]
pub async fn create_user(db: Data<MongoRepo>, new_user: Json<User>) -> HttpResponse {
//create_user code goes here
}

#[get("/user/{id}")]
pub async fn get_user(db: Data<MongoRepo>, path: Path<String>) -> HttpResponse {
let id = path.into_inner();
if id.is_empty() {
return HttpResponse::BadRequest().body("invalid ID");
}
let user_detail = db.get_user(&id).await;
match user_detail {
Ok(user) => HttpResponse::Ok().json(user),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}

上面的代码片段执行以下操作:

  • 修改依赖项以包含getPath
  • 使用路由宏指定HTTP方法、对应的路由和路由参数
  • 创建一个get_user处理程序,它接收db(数据库连接或实例)、MongoRepo(数据库操作仓库的实例)以及path(路由路径参数)作为输入。在处理程序内部,我们首先从请求中提取用户ID并存储在变量id中,然后调用db(或MongoRepo的)get_user方法根据ID获取用户信息。若请求成功且用户信息被检索到,则返回包含用户信息的正确响应;若请求失败或发生错误,则返回相应的错误响应。

最后,我们需要修改应用程序入口点(main.rsget_user以通过导入处理程序并为其添加新服务来包含该处理程序。

mod api;
mod models;
mod repository;

//modify imports below
use actix_web::{web::Data, App, HttpServer};
use api::user_api::{create_user, get_user}; //import the handler here
use repository::mongodb_repo::MongoRepo;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
let db = MongoRepo::init().await;
let db_data = Data::new(db);
HttpServer::new(move || {
App::new()
.app_data(db_data.clone())
.service(create_user)
.service(get_user) //add this
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}

编辑用户端点
要编辑用户,我们必须首先通过edit_user向实现块mongodb_repo.rs添加方法来修改文件。

use std::env;
extern crate dotenv;
use dotenv::dotenv;

use mongodb::{
bson::{extjson::de::Error, oid::ObjectId, doc},
results::{ InsertOneResult, UpdateResult}, //modify here
Client, Collection,
};
use crate::models::user_model::User;

pub struct MongoRepo {
col: Collection<User>,
}

impl MongoRepo {
pub async fn init() -> Self {
//init code goes here
}

pub async fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
//create_user code goes here
}

pub async fn get_user(&self, id: &String) -> Result<User, Error> {
//get_user code goes here
}

pub async fn update_user(&self, id: &String, new_user: User) -> Result<UpdateResult, Error> {
let obj_id = ObjectId::parse_str(id).unwrap();
let filter = doc! {"_id": obj_id};
let new_doc = doc! {
"$set":
{
"id": new_user.id,
"name": new_user.name,
"location": new_user.location,
"title": new_user.title
},
};
let updated_doc = self
.col
.update_one(filter, new_doc, None)
.await
.ok()
.expect("Error updating user");
Ok(updated_doc)
}
}

上面的代码片段执行以下操作:

  • 修改依赖项以包含UpdateResult
  • 添加一个update_user方法,该方法接受 selfidnew_user参数并返回更新的用户详细信息或错误。在该方法内部,我们将 id转换为ObjectId,创建一个filter 变量来获取我们想要更新的匹配文档,并使用doc宏来更新文档字段。然后,我们使用self引用该MongoRepo结构体来访问update_one集合中的函数,以更新filter与指定匹配的用户并处理错误。最后我们返回了更新后的用户信息。

其次,我们需要user_api.rs通过创建一个处理程序来进行修改,该处理程序使用 中的update_user方法repository来更新用户。

use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use actix_web::{
post, get, put, //modify here
web::{Data, Json, Path},
HttpResponse,
};
use mongodb::bson::oid::ObjectId; //add this

#[post("/user")]
pub async fn create_user(db: Data<MongoRepo>, new_user: Json<User>) -> HttpResponse {
//create_user code goes here
}

#[get("/user/{id}")]
pub async fn get_user(db: Data<MongoRepo>, path: Path<String>) -> HttpResponse {
//get_user code goes here
}

#[put("/user/{id}")]
pub async fn update_user(
db: Data<MongoRepo>,
path: Path<String>,
new_user: Json<User>,
) -> HttpResponse {
let id = path.into_inner();
if id.is_empty() {
return HttpResponse::BadRequest().body("invalid ID");
};
let data = User {
id: Some(ObjectId::parse_str(&id).unwrap()),
name: new_user.name.to_owned(),
location: new_user.location.to_owned(),
title: new_user.title.to_owned(),
};
let update_result = db.update_user(&id, data).await;
match update_result {
Ok(update) => {
if update.matched_count == 1 {
let updated_user_info = db.get_user(&id).await;
return match updated_user_info {
Ok(user) => HttpResponse::Ok().json(user),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
};
} else {
return HttpResponse::NotFound().body("No user found with specified ID");
}
}
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}

上面的代码片段执行以下操作:

  • 修改依赖项以包含putObjectId
  • 使用路由宏指定HTTP方法、对应的路由和路由参数
  • 创建一个update_user处理程序,该处理程序接受MongoRepopathdb的类型作为参数。在处理程序内部,我们创建了一个变量来获取用户的id ,并使用该方法通过传入更新的用户信息来更新数据库中的用户详细信息。最后,我们会验证更新操作是否顺利完成,并根据结果返回更新后的用户信息或相应的错误信息(如果更新过程中出现了任何问题)。

最后,我们需要修改应用程序入口点(main.rs)以通过导入处理程序并为其添加新服务来包含该处理程序。

 mod api;
mod models;
mod repository;

//modify imports below
use actix_web::{web::Data, App, HttpServer};
use api::user_api::{create_user, get_user, update_user}; //import the handler here
use repository::mongodb_repo::MongoRepo;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
let db = MongoRepo::init().await;
let db_data = Data::new(db);
HttpServer::new(move || {
App::new()
.app_data(db_data.clone())
.service(create_user)
.service(get_user)
.service(update_user) //add this
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}

删除用户端点
要删除用户,我们必须首先通过delete_user向实现块mongodb_repo.rs添加方法来修改文件。

use std::env;
extern crate dotenv;
use dotenv::dotenv;

use mongodb::{
bson::{extjson::de::Error, oid::ObjectId, doc},
results::{ InsertOneResult, UpdateResult, DeleteResult}, //modify here
Client, Collection,
};
use crate::models::user_model::User;

pub struct MongoRepo {
col: Collection<User>,
}

impl MongoRepo {
pub async fn init() -> Self {
//init code goes here
}

pub async fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
//create_user code goes here
}

pub async fn get_user(&self, id: &String) -> Result<User, Error> {
//get_user code goes here
}

pub async fn update_user(&self, id: &String, new_user: User) -> Result<UpdateResult, Error> {
//update_user code goes here
}

pub async fn delete_user(&self, id: &String) -> Result<DeleteResult, Error> {
let obj_id = ObjectId::parse_str(id).unwrap();
let filter = doc! {"_id": obj_id};
let user_detail = self
.col
.delete_one(filter, None)
.await
.ok()
.expect("Error deleting user");
Ok(user_detail)
}
}

上面的代码片段执行以下操作:

  • 修改依赖项以包含DeleteResult
  • 添加一个delete_user方法,该方法接受 selfid作为参数并返回已删除的用户详细信息或错误。在该方法内部,我们将 转换idObjectId并创建一个filter 变量来获取我们要删除的匹配文档。然后,我们使用self引用该MongoRepo结构体来访问delete_one集合中的函数,以删除filter与指定匹配的用户并处理错误。最后我们返回了删除的用户信息。

接下来,我们需要在user_api.rs中创建一个处理程序,这个处理程序会调用repository中的某个方法(我们假设这个方法命名为delete_user)来删除指定的用户。

use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use actix_web::{
post, get, put, delete, //modify here
web::{Data, Json, Path},
HttpResponse,
};
use mongodb::bson::oid::ObjectId; //add this

#[post("/user")]
pub async fn create_user(db: Data<MongoRepo>, new_user: Json<User>) -> HttpResponse {
//create_user code goes here
}

#[get("/user/{id}")]
pub async fn get_user(db: Data<MongoRepo>, path: Path<String>) -> HttpResponse {
//get_user code goes here
}

#[put("/user/{id}")]
pub async fn update_user(
db: Data<MongoRepo>,
path: Path<String>,
new_user: Json<User>,
) -> HttpResponse {
//update_user code goes here
}

#[delete("/user/{id}")]
pub async fn delete_user(db: Data<MongoRepo>, path: Path<String>) -> HttpResponse {
let id = path.into_inner();
if id.is_empty() {
return HttpResponse::BadRequest().body("invalid ID");
};
let result = db.delete_user(&id).await;
match result {
Ok(res) => {
if res.deleted_count == 1 {
return HttpResponse::Ok().json("User successfully deleted!");
} else {
return HttpResponse::NotFound().json("User with specified ID not found!");
}
}
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}

上面的代码片段执行以下操作:

  • 修改依赖项以包含delete
  • 使用路由宏指定HTTP方法、对应的路由和路由参数
  • 创建一个delete_user处理程序,该处理程序接受MongoRepopathdb类型作为参数。在处理程序内部,我们创建了一个名为id的变量来获取需要删除的用户ID。然后,我们调用了repository中的delete_user方法,并将id作为参数传递进去,以删除指定的用户。最后,我们根据删除操作的结果返回了相应的响应或错误(如果有)。

最后,我们需要修改应用程序入口点(main.rs)以通过导入处理程序并为其添加新服务来包含该处理程序。

mod api;
mod models;
mod repository;

//modify imports below
use actix_web::{web::Data, App, HttpServer};
use api::user_api::{create_user, get_user, update_user, delete_user}; //import the handler here
use repository::mongodb_repo::MongoRepo;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
let db = MongoRepo::init().await;
let db_data = Data::new(db);
HttpServer::new(move || {
App::new()
.app_data(db_data.clone())
.service(create_user)
.service(get_user)
.service(update_user)
.service(delete_user) //add this
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}

获取所有用户端点
要获取用户列表,我们必须首先通过get_all_users向实现块mongodb_repo.rs添加方法来修改文件。

 use std::env;
extern crate dotenv;
use dotenv::dotenv;

use mongodb::{
bson::{extjson::de::Error, oid::ObjectId, doc},
results::{ InsertOneResult, UpdateResult, DeleteResult},
Client, Collection,
};
use futures::stream::TryStreamExt; //add this
use crate::models::user_model::User;

pub struct MongoRepo {
col: Collection<User>,
}

impl MongoRepo {
pub async fn init() -> Self {
//init code goes here
}

pub async fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
//create_user code goes here
}

pub async fn get_user(&self, id: &String) -> Result<User, Error> {
//get_user code goes here
}

pub async fn update_user(&self, id: &String, new_user: User) -> Result<UpdateResult, Error> {
//update_user code goes here
}

pub async fn delete_user(&self, id: &String) -> Result<DeleteResult, Error> {
//delete_user code goes here
}

pub async fn get_all_users(&self) -> Result<Vec<User>, Error> {
let mut cursors = self
.col
.find(None, None)
.await
.ok()
.expect("Error getting list of users");
let mut users: Vec<User> = Vec::new();
while let Some(user) = cursors
.try_next()
.await
.ok()
.expect("Error mapping through cursor")
{
users.push(user)
}
Ok(users)
}
}

上面的代码片段添加了一个get_all_users方法,该方法接受 a self作为参数并返回用户列表或错误。在方法内部,我们使用self引用结构MongoRepo体从集合中访问find函数,无需任何过滤器,以便它可以匹配数据库内的所有文档,使用该try_next()方法循环遍历用户列表以最佳方式返回列表,并处理错误。

其次,我们需要在user_api.rs中创建一个新的处理程序,这个处理程序会利用repository中提供的get_all_users方法来获取用户列表。

use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use actix_web::{
post, get, put, delete,
web::{Data, Json, Path},
HttpResponse,
};
use mongodb::bson::oid::ObjectId;

#[post("/user")]
pub async fn create_user(db: Data<MongoRepo>, new_user: Json<User>) -> HttpResponse {
//create_user code goes here
}

#[get("/user/{id}")]
pub async fn get_user(db: Data<MongoRepo>, path: Path<String>) -> HttpResponse {
//get_user code goes here
}

#[put("/user/{id}")]
pub async fn update_user(
db: Data<MongoRepo>,
path: Path<String>,
new_user: Json<User>,
) -> HttpResponse {
//update_user code goes here
}

#[delete("/user/{id}")]
pub async fn delete_user(db: Data<MongoRepo>, path: Path<String>) -> HttpResponse {
//delet_user code goes here
}

#[get("/users")]
pub async fn get_all_users(db: Data<MongoRepo>) -> HttpResponse {
let users = db.get_all_users().await;
match users {
Ok(users) => HttpResponse::Ok().json(users),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}

上面的代码片段执行以下操作:

  • 使用路由宏指定HTTP方法和对应的路由
  • 创建一个get_all_users使用该db.delete_user方法获取用户列表的处理程序。然后,我们返回用户列表或错误(如果有)。

最终,我们需要更新应用程序的入口文件main.rs,通过导入新的处理程序,并将它们作为新的服务添加到服务集合中,以确保它们能够被正确地路由和处理。

mod api;
mod models;
mod repository;

//modify imports below
use actix_web::{web::Data, App, HttpServer};
use api::user_api::{create_user, get_user, update_user, delete_user, get_all_users}; //import the handler here
use repository::mongodb_repo::MongoRepo;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
let db = MongoRepo::init().await;
let db_data = Data::new(db);
HttpServer::new(move || {
App::new()
.app_data(db_data.clone())
.service(create_user)
.service(get_user)
.service(update_user)
.service(delete_user)
.service(get_all_users)//add this
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}

完成后,我们可以通过在终端中运行以下命令来测试我们的应用程序。

cargo run
创建用户端点
获取用户端点
编辑用户端点
删除用户端点
获取用户端点列表
包含用户文档的数据库

结论

这篇文章讨论了如何模块化 Rust 应用程序、构建 REST API 以及使用 MongoDB 保存我们的数据。

原文链接:https://dev.to/hackmamba/build-a-rest-api-with-rust-and-mongodb-actix-web-version-ei1

#你可能也喜欢这些API文章!