Rust 🦀 使用 Docker 的 CRUD Rest API 🐳

2025-06-05

Rust 🦀 使用 Docker 的 CRUD Rest API 🐳

让我们使用 Rust 创建 CRUD Rest API:

  • 没有具体的框架
  • Serde 用于序列化和反序列化 JSON
  • Postgres(数据库)
  • Docker
  • Docker Compose

如果您更喜欢视频版本:

所有代码均可在 GitHub 存储库中找到(链接在视频描述中):https://youtu.be/vhNoiBOuW94


🏁 简介

以下是我们要创建的应用程序架构图:

使用 Laravel、Postgres、Docker 和 Docker Compose 的 PHP CRUD Rest API。使用 Postman 和 Tableplus 进行测试

我们将为基本的 CRUD 操作创建五个端点:

  • 创造
  • 阅读全部
  • 阅读一篇
  • 更新
  • 删除

我们将使用 Postgres 作为数据库,并使用 Docker 和 Docker Compose 来运行应用程序。

我们将使用 Postman 测试端点并使用 Tableplus 检查数据库。

👣 步骤

我们将提供一步一步的指南,以便您可以跟随。

步骤如下:

  1. 检查先决条件
  2. 项目创建和依赖项安装
  3. 对应用程序进行编码
  4. 使用 Docker 运行 Postgres 数据库
  5. 使用 Docker Compose 构建并运行应用程序
  6. 使用 Postman 和 TablePlus 测试应用程序

💡 先决条件

  • 已安装 Rust 编译器(版本 1.51+)
  • 已安装货物(版本 1.51+)
  • docker 安装(版本 20.10+)
  • [可选] 安装 VS Code(或任何你喜欢的 IDE)
  • [可选] Postman 或任何 API 测试工具
  • [可选] Tableplus 或任何数据库客户端

🚀 创建一个新的 Rust 项目

要创建一个新的 Rust 项目,我们将使用 CLI。



cargo new rust-crud-api


Enter fullscreen mode Exit fullscreen mode

进入项目文件夹:



cd rust-crud-api


Enter fullscreen mode Exit fullscreen mode

并使用您喜欢的 IDE 打开项目。如果您使用 VS Code,则可以使用以下命令:



code .


Enter fullscreen mode Exit fullscreen mode

打开所调用的文件Cargo.toml并添加以下依赖项:



postgres = "0.19"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"


Enter fullscreen mode Exit fullscreen mode

postgres是 Rust 的 Postgres 驱动程序。
serde是一个用于序列化和反序列化的库。
serde_json是一个特定于 JSON 的库。
serde_derive是一个用于派生序列化和反序列化特征(宏)的库

您的Cargo.toml文件应如下所示:



[package]
name = "rust-crud-api"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
postgres = "0.19"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"


Enter fullscreen mode Exit fullscreen mode

请注意,包名称可能根据您为项目指定的名称而有所不同。

您的项目现在应该如下所示:

Rust 项目结构

我们现在准备对应用程序进行编码。


👩‍💻编写应用程序代码

我们将一步一步来:

  1. 导入依赖项。
  2. 创建模型(具有 ID、姓名和电子邮件的用户)并添加常量。
  3. 主要功能:数据库连接和TCP服务器。
  4. 实用函数:set_database、get_id、get_user_request_body。
  5. 在函数中创建路由(端点)。
  6. 创建实用函数。
  7. 创建控制器。

对于这个项目,我们将把所有内容编写在一个约 200 行代码的文件中。

这不是最佳实践,但它将帮助我们专注于 Rust 代码,而不是项目结构。

所有代码均可在GitHub上找到(链接在视频说明中)。

⬇️ 导入依赖项

打开main.rs file,删除所有代码,并添加以下导入:



use postgres::{ Client, NoTls };
use postgres::Error as PostgresError;
use std::net::{ TcpListener, TcpStream };
use std::io::{ Read, Write };
use std::env;

#[macro_use]
extern crate serde_derive;


Enter fullscreen mode Exit fullscreen mode

Client用于连接数据库。
NoTls用于不使用 TLS 连接数据库。
PostgresError是 Postgres 驱动程序返回的错误类型。
TcpListenerTcpStream创建一个 TCP 服务器。
ReadWrite用于从 TCP 流中读取和写入。
env用于读取环境变量。

#[macro_use]属性用于导入serde_derive宏。

我们将用它来推导我们的模型SerializeDeserialize特征。

🥻 创建模型

在导入的正下方,添加以下代码:



//Model: User struct with id, name, email
#[derive(Serialize, Deserialize)]
struct User {
    id: Option<i32>,
    name: String,
    email: String,
}


Enter fullscreen mode Exit fullscreen mode

我们将使用该模型来表示我们应用程序中的用户。

  • id是一个整数,并且是可选的。原因是我们在创建或更新新用户时没有提供 ID。数据库会为我们生成 ID。但我们仍然希望在获取用户时返回带有 ID 的用户。

  • name是一个字符串,并且是必需的。我们将用它来存储用户的名称。

email是一个字符串,并且是必填项。我们将用它来存储用户的电子邮件(无需检查其是否有效)。

🪨常量

在模型下方,添加以下常量:



//DATABASE URL
const DB_URL: &str = env!("DATABASE_URL");

//cosntants
const OK_RESPONSE: &str = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n";
const NOT_FOUND: &str = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
const INTERNAL_ERROR: &str = "HTTP/1.1 500 INTERNAL ERROR\r\n\r\n";


Enter fullscreen mode Exit fullscreen mode
  • DB_URL是数据库的 URL。我们将从环境变量中读取它。在本例中,我们将标头添加Content-Type: application/json到响应中。
  • OK_RESPONSE, NOT_FOUNDINTERNAL_ERROR是我们将发送回客户端的响应。我们将使用它们来返回状态码和内容类型。

🏠 主要功能

在常量下方添加以下代码:



//main function
fn main() {
    //Set Database
    if let Err(_) = set_database() {
        println!("Error setting database");
        return;
    }

    //start server and print port
    let listener = TcpListener::bind(format!("0.0.0.0:8080")).unwrap();
    println!("Server listening on port 8080");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                handle_client(stream);
            }
            Err(e) => {
                println!("Unable to connect: {}", e);
            }
        }
    }
}


Enter fullscreen mode Exit fullscreen mode
  • set_database是我们稍后要创建的函数。它将用于连接数据库。
  • TcpListener::bind用于在端口 8080 上创建 TCP 服务器。
  • listener.incoming()用于获取传入连接。

⛑️ 实用函数

现在,在主函数之外,添加以下三个实用函数:



//db setup
fn set_database() -> Result<(), PostgresError> {
    let mut client = Client::connect(DB_URL, NoTls)?;
    client.batch_execute(
        "
        CREATE TABLE IF NOT EXISTS users (
            id SERIAL PRIMARY KEY,
            name VARCHAR NOT NULL,
            email VARCHAR NOT NULL
        )
    "
    )?;
    Ok(())
}

//Get id from request URL
fn get_id(request: &str) -> &str {
    request.split("/").nth(2).unwrap_or_default().split_whitespace().next().unwrap_or_default()
}

//deserialize user from request body without id
fn get_user_request_body(request: &str) -> Result<User, serde_json::Error> {
    serde_json::from_str(request.split("\r\n\r\n").last().unwrap_or_default())
}


Enter fullscreen mode Exit fullscreen mode
  • set_database连接到数据库,users如果表不存在则创建表。
  • get_id用于从请求URL中获取id。
  • get_user_request_body用于从CreateUpdate端点的请求主体(不带id)反序列化用户。

🚦 处理客户端

在主函数和实用函数之间,添加以下代码(不用担心,文章末尾会有最终代码):



//handle requests
fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 1024];
    let mut request = String::new();

    match stream.read(&mut buffer) {
        Ok(size) => {
            request.push_str(String::from_utf8_lossy(&buffer[..size]).as_ref());

            let (status_line, content) = match &*request {
                r if r.starts_with("POST /users") => handle_post_request(r),
                r if r.starts_with("GET /users/") => handle_get_request(r),
                r if r.starts_with("GET /users") => handle_get_all_request(r),
                r if r.starts_with("PUT /users/") => handle_put_request(r),
                r if r.starts_with("DELETE /users/") => handle_delete_request(r),
                _ => (NOT_FOUND.to_string(), "404 not found".to_string()),
            };

            stream.write_all(format!("{}{}", status_line, content).as_bytes()).unwrap();
        }
        Err(e) => eprintln!("Unable to read stream: {}", e),
    }
}


Enter fullscreen mode Exit fullscreen mode

我们为传入的请求创建一个缓冲区,然后创建一个字符串。

使用matchRust 中的语句,我们可以检查请求并调用正确的函数来处理它。

如果没有匹配,我们会发回404错误。

最后,我们设置流将响应写回客户端并处理任何错误。

🎛️ 控制器

现在,让我们创建处理请求的函数。

它们有五个函数,每个端点一个:

  • handle_post_request对于Create端点
  • handle_get_request对于Read端点
  • handle_get_all_request对于Read All端点
  • handle_put_request对于Update端点
  • handle_delete_request对于Delete端点

在函数下方添加代码handle_client



//handle post request
fn handle_post_request(request: &str) -> (String, String) {
    match (get_user_request_body(&request), Client::connect(DB_URL, NoTls)) {
        (Ok(user), Ok(mut client)) => {
            client
                .execute(
                    "INSERT INTO users (name, email) VALUES ($1, $2)",
                    &[&user.name, &user.email]
                )
                .unwrap();

            (OK_RESPONSE.to_string(), "User created".to_string())
        }
        _ => (INTERNAL_ERROR.to_string(), "Internal error".to_string()),
    }
}

//handle get request
fn handle_get_request(request: &str) -> (String, String) {
    match (get_id(&request).parse::<i32>(), Client::connect(DB_URL, NoTls)) {
        (Ok(id), Ok(mut client)) =>
            match client.query_one("SELECT * FROM users WHERE id = $1", &[&id]) {
                Ok(row) => {
                    let user = User {
                        id: row.get(0),
                        name: row.get(1),
                        email: row.get(2),
                    };

                    (OK_RESPONSE.to_string(), serde_json::to_string(&user).unwrap())
                }
                _ => (NOT_FOUND.to_string(), "User not found".to_string()),
            }

        _ => (INTERNAL_ERROR.to_string(), "Internal error".to_string()),
    }
}

//handle get all request
fn handle_get_all_request(_request: &str) -> (String, String) {
    match Client::connect(DB_URL, NoTls) {
        Ok(mut client) => {
            let mut users = Vec::new();

            for row in client.query("SELECT id, name, email FROM users", &[]).unwrap() {
                users.push(User {
                    id: row.get(0),
                    name: row.get(1),
                    email: row.get(2),
                });
            }

            (OK_RESPONSE.to_string(), serde_json::to_string(&users).unwrap())
        }
        _ => (INTERNAL_ERROR.to_string(), "Internal error".to_string()),
    }
}

//handle put request
fn handle_put_request(request: &str) -> (String, String) {
    match
        (
            get_id(&request).parse::<i32>(),
            get_user_request_body(&request),
            Client::connect(DB_URL, NoTls),
        )
    {
        (Ok(id), Ok(user), Ok(mut client)) => {
            client
                .execute(
                    "UPDATE users SET name = $1, email = $2 WHERE id = $3",
                    &[&user.name, &user.email, &id]
                )
                .unwrap();

            (OK_RESPONSE.to_string(), "User updated".to_string())
        }
        _ => (INTERNAL_ERROR.to_string(), "Internal error".to_string()),
    }
}

//handle delete request
fn handle_delete_request(request: &str) -> (String, String) {
    match (get_id(&request).parse::<i32>(), Client::connect(DB_URL, NoTls)) {
        (Ok(id), Ok(mut client)) => {
            let rows_affected = client.execute("DELETE FROM users WHERE id = $1", &[&id]).unwrap();

            //if rows affected is 0, user not found
            if rows_affected == 0 {
                return (NOT_FOUND.to_string(), "User not found".to_string());
            }

            (OK_RESPONSE.to_string(), "User deleted".to_string())
        }
        _ => (INTERNAL_ERROR.to_string(), "Internal error".to_string()),
    }
}


Enter fullscreen mode Exit fullscreen mode
  • 有些使用get_id函数从请求URL中获取id。

  • get_user_request_body函数用于从请求体中获取JSON格式的用户,并将其反序列化为User结构体。

  • 如果请求无效或数据库连接失败,则会进行一些错误处理。

📝 回顾

完整文件如下main.rs



use postgres::{ Client, NoTls };
use postgres::Error as PostgresError;
use std::net::{ TcpListener, TcpStream };
use std::io::{ Read, Write };
use std::env;

#[macro_use]
extern crate serde_derive;

//Model: User struct with id, name, email
#[derive(Serialize, Deserialize)]
struct User {
    id: Option<i32>,
    name: String,
    email: String,
}

//DATABASE URL
const DB_URL: &str = env!("DATABASE_URL");

//constants
const OK_RESPONSE: &str = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n";
const NOT_FOUND: &str = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
const INTERNAL_ERROR: &str = "HTTP/1.1 500 INTERNAL ERROR\r\n\r\n";

//main function
fn main() {
    //Set Database
    if let Err(_) = set_database() {
        println!("Error setting database");
        return;
    }

    //start server and print port
    let listener = TcpListener::bind(format!("0.0.0.0:8080")).unwrap();
    println!("Server listening on port 8080");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                handle_client(stream);
            }
            Err(e) => {
                println!("Unable to connect: {}", e);
            }
        }
    }
}

//handle requests
fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 1024];
    let mut request = String::new();

    match stream.read(&mut buffer) {
        Ok(size) => {
            request.push_str(String::from_utf8_lossy(&buffer[..size]).as_ref());

            let (status_line, content) = match &*request {
                r if r.starts_with("POST /users") => handle_post_request(r),
                r if r.starts_with("GET /users/") => handle_get_request(r),
                r if r.starts_with("GET /users") => handle_get_all_request(r),
                r if r.starts_with("PUT /users/") => handle_put_request(r),
                r if r.starts_with("DELETE /users/") => handle_delete_request(r),
                _ => (NOT_FOUND.to_string(), "404 not found".to_string()),
            };

            stream.write_all(format!("{}{}", status_line, content).as_bytes()).unwrap();
        }
        Err(e) => eprintln!("Unable to read stream: {}", e),
    }
}

//handle post request
fn handle_post_request(request: &str) -> (String, String) {
    match (get_user_request_body(&request), Client::connect(DB_URL, NoTls)) {
        (Ok(user), Ok(mut client)) => {
            client
                .execute(
                    "INSERT INTO users (name, email) VALUES ($1, $2)",
                    &[&user.name, &user.email]
                )
                .unwrap();

            (OK_RESPONSE.to_string(), "User created".to_string())
        }
        _ => (INTERNAL_ERROR.to_string(), "Internal error".to_string()),
    }
}

//handle get request
fn handle_get_request(request: &str) -> (String, String) {
    match (get_id(&request).parse::<i32>(), Client::connect(DB_URL, NoTls)) {
        (Ok(id), Ok(mut client)) =>
            match client.query_one("SELECT * FROM users WHERE id = $1", &[&id]) {
                Ok(row) => {
                    let user = User {
                        id: row.get(0),
                        name: row.get(1),
                        email: row.get(2),
                    };

                    (OK_RESPONSE.to_string(), serde_json::to_string(&user).unwrap())
                }
                _ => (NOT_FOUND.to_string(), "User not found".to_string()),
            }

        _ => (INTERNAL_ERROR.to_string(), "Internal error".to_string()),
    }
}

//handle get all request
fn handle_get_all_request(_request: &str) -> (String, String) {
    match Client::connect(DB_URL, NoTls) {
        Ok(mut client) => {
            let mut users = Vec::new();

            for row in client.query("SELECT id, name, email FROM users", &[]).unwrap() {
                users.push(User {
                    id: row.get(0),
                    name: row.get(1),
                    email: row.get(2),
                });
            }

            (OK_RESPONSE.to_string(), serde_json::to_string(&users).unwrap())
        }
        _ => (INTERNAL_ERROR.to_string(), "Internal error".to_string()),
    }
}

//handle put request
fn handle_put_request(request: &str) -> (String, String) {
    match
        (
            get_id(&request).parse::<i32>(),
            get_user_request_body(&request),
            Client::connect(DB_URL, NoTls),
        )
    {
        (Ok(id), Ok(user), Ok(mut client)) => {
            client
                .execute(
                    "UPDATE users SET name = $1, email = $2 WHERE id = $3",
                    &[&user.name, &user.email, &id]
                )
                .unwrap();

            (OK_RESPONSE.to_string(), "User updated".to_string())
        }
        _ => (INTERNAL_ERROR.to_string(), "Internal error".to_string()),
    }
}

//handle delete request
fn handle_delete_request(request: &str) -> (String, String) {
    match (get_id(&request).parse::<i32>(), Client::connect(DB_URL, NoTls)) {
        (Ok(id), Ok(mut client)) => {
            let rows_affected = client.execute("DELETE FROM users WHERE id = $1", &[&id]).unwrap();

            //if rows affected is 0, user not found
            if rows_affected == 0 {
                return (NOT_FOUND.to_string(), "User not found".to_string());
            }

            (OK_RESPONSE.to_string(), "User deleted".to_string())
        }
        _ => (INTERNAL_ERROR.to_string(), "Internal error".to_string()),
    }
}

//db setup
fn set_database() -> Result<(), PostgresError> {
    let mut client = Client::connect(DB_URL, NoTls)?;
    client.batch_execute(
        "
        CREATE TABLE IF NOT EXISTS users (
            id SERIAL PRIMARY KEY,
            name VARCHAR NOT NULL,
            email VARCHAR NOT NULL
        )
    "
    )?;
    Ok(())
}

//Get id from request URL
fn get_id(request: &str) -> &str {
    request.split("/").nth(2).unwrap_or_default().split_whitespace().next().unwrap_or_default()
}

//deserialize user from request body without id
fn get_user_request_body(request: &str) -> Result<User, serde_json::Error> {
    serde_json::from_str(request.split("\r\n\r\n").last().unwrap_or_default())
}


Enter fullscreen mode Exit fullscreen mode

我们已经完成了应用程序代码。现在轮到 Docker 了。

🐳 Docker

我们将直接在镜像中构建 Rust 应用程序。我们将使用官方 Rust 镜像作为基础镜像。我们还将使用官方 Postgres 镜像作为数据库的基础镜像。

我们将创建三个文件:

  • .dockerignore:忽略我们不想在镜像文件系统中复制的文件和文件夹
  • Dockerfile:构建 Rust 镜像
  • docker-compose.yml:运行 Rust 和 Postgres 服务(容器)

您可以使用终端或代码编辑器创建它们。



touch .dockerignore Dockerfile docker-compose.yml


Enter fullscreen mode Exit fullscreen mode

🚫 .dockerignore

打开.dockerignore文件并添加以下内容:



**/target


Enter fullscreen mode Exit fullscreen mode

这是为了避免复制图像文件系统中的目标文件夹。

🐋 Dockerfile

我们将使用多阶段构建。我们将:

  • 构建阶段:构建 Rust 应用程序
  • 生产阶段:运行 Rust 应用程序

打开Dockerfile并添加以下内容(注释中有解释):



# Build stage
FROM rust:1.69-buster as builder

WORKDIR /app

# Accept the build argument
ARG DATABASE_URL

# Make sure to use the ARG in ENV
ENV DATABASE_URL=$DATABASE_URL

# Copy the source code
COPY . .

# Build the application
RUN cargo build --release


# Production stage
FROM debian:buster-slim

WORKDIR /usr/local/bin

COPY --from=builder /app/target/release/rust-crud-api .

CMD ["./rust-crud-api"]


Enter fullscreen mode Exit fullscreen mode

请注意,我们使用的rust-crud-api是可执行文件的名称。这是项目文件夹的名称。如果您使用其他名称,请更改。

🐙 docker-compose.yml

docker-compose.yml使用以下内容填充文件:



version: '3.9'

services:
  rustapp:
    container_name: rustapp
    image: francescoxx/rustapp:1.0.0
    build:
      context: .
      dockerfile: Dockerfile
      args:
        DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
    ports:
      - '8080:8080'
    depends_on:
      - db

  db:
    container_name: db
    image: 'postgres:12'
    ports:
      - '5432:5432'
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
      - POSTGRES_DB=postgres
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata: {}


Enter fullscreen mode Exit fullscreen mode
  • 我们有两个服务,rustappdb。该rustapp服务使用我们之前创建的 Dockerfile 构建。该db服务使用官方 Postgres 镜像。我们使用depends_on属性来确保db服务在 服务之前启动rustapp

  • 请注意,DATABASE_URLbuild 参数设置为postgres://postgres:postgres@db:5432/postgres.db是 Postgres 容器的服务名称(和 container_name),以便它将被解析为容器 IP 地址。

  • 我们使用该arg属性将DATABASE_URL构建参数传递给 Dockerfile。

  • 我们还使用命名卷pg_data来保存数据库数据。

现在是时候构建图像并运行容器了。

🏗️ 构建镜像并运行容器

我们只需要再执行三个步骤:

  • 运行 postgres 容器
  • 构建 Rust 应用程序镜像
  • 运行 Rust 应用程序容器

🐘 运行 Postgres 容器

首先,运行 postgres 容器:



docker-compose up -d db


Enter fullscreen mode Exit fullscreen mode

这将从 DockerHub 拉取(下载)图像并在我们的机器上运行它。

要查看日志,您可以输入



docker-compose logs db


Enter fullscreen mode Exit fullscreen mode

如果有类似这样的内容,则表示数据库在容器中启动并运行(日志的最后一行应该显示:“数据库系统已准备好接受连接”)

Rust 项目结构

🏗️ 构建 Rust 应用程序镜像

现在是时候构建 Rust 应用程序镜像了。我们将使用docker-compose build命令。
它将使用我们之前创建的 Dockerfile 来构建镜像。

(注意:我们可能会输入docker compose up,但这样做,我们将无法理解发生了什么。简而言之,当我们输入时docker compose up,Docker 会在需要时构建图像,然后运行容器)。



docker compose build


Enter fullscreen mode Exit fullscreen mode

这需要时间,因为我们在图像中构建 Rust 应用程序。

大约 150 秒后(!),我们应该已经构建好了图像。

Rust 项目结构

👟 运行 Rust 容器

现在我们可以运行 Rust 容器:



docker compose up rustapp


Enter fullscreen mode Exit fullscreen mode

您可以通过打开另一个终端并输入以下内容来检查两个容器:



docker ps -a


Enter fullscreen mode Exit fullscreen mode

最后,您可以通过输入以下命令检查 postgres 数据库:



docker exec -it db psql -U postgres
\dt
select * from users;


Enter fullscreen mode Exit fullscreen mode

以下是输出的屏幕截图:

Rust 项目结构

现在是时候测试我们的应用程序了。

🧪 测试应用程序

为了测试该应用程序,我们将使用 Postman。您可以从此处下载

📝 测试数据库连接

由于我们没有专用端点来测试数据库连接,因此我们将创建一个GET request to http://localhost:8080/users

输出应该是[]。这是正确的,因为数据库是空的。

GET 请求到 http://localhost:8080/users

📝 创建新用户

要创建新用户,请使用POST request to http://localhost:8080/users以下内容:

⚠️ 在请求中添加标头“Content-Type: application/json”



{
    "name": "aaa",
    "email": "aaa@mail"
}


Enter fullscreen mode Exit fullscreen mode

POST 请求至 http://localhost:8080/users

在同一端点上创建另外两个具有以下主体的用户,从而POST request to http://localhost:8080/users



{
    "name": "bbb",
    "email": "bbb@mail"
}


Enter fullscreen mode Exit fullscreen mode


{
    "name": "ccc",
    "email": "ccc@mail"
}


Enter fullscreen mode Exit fullscreen mode

📝 获取所有用户

要获取所有用户,请制作GET request to http://localhost:8080/users

GET 请求到 http://localhost:8080/users

📝 获取单个用户(带错误处理)

要获取单个用户,我们可以在 URL 中指定 id。

例如,为了获取 ID 为 1 的用户,我们可以GET request to http://localhost:8080/users/1

GET 请求到 http://localhost:8080/users/1

请注意,如果我们尝试获取具有不存在的 ID 的用户,则会收到错误。

做一个GET request to http://localhost:8080/users/10

GET 请求至 http://localhost:8080/users/10

如果我们尝试使用字符串而不是整数来获取用户 py,我们也会收到错误。

做一个GET request to http://localhost:8080/users/aaa

GET 请求至 http://localhost:8080/users/aaa

📝 更新用户

我们必须在 URL 中传递一个 id,并在 body 中传递新数据来更新现有用户。

例如,制作一个PUT request to http://localhost:8080/users/2具有以下主体的文件:



{
    "name": "NEW",
    "email": "NEW@mail"
}


Enter fullscreen mode Exit fullscreen mode

将请求发送到 http://localhost:8080/users/1

📝 删除用户

最后,要删除用户,我们需要在 URL 中传递 id。

例如,制作一个DELETE request to http://localhost:8080/users/3

删除请求 http://localhost:8080/users/3

🐢 使用 TablePlus 进行测试

您还可以使用 TablePlus 测试应用程序。

使用以下凭据创建一个新的 Postgres 连接:

  • 主机:localhost
  • 端口:5432
  • 用户:postgres
  • 密码:postgres
  • 数据库:postgres

然后点击connect右下角的按钮。

TablePlus

这将打开一个包含数据库的新窗口。

您可以检查users表格并查看数据是否在那里。

TablePlus

完毕。

🏁 结论

我们成功了!

我们使用 Rust、Serde、Postgres 和 Docker 创建了一个 REST API。

如果您更喜欢视频版本:

所有代码均可在 GitHub 存储库中找到(链接在视频描述中):https://youtu.be/vhNoiBOuW94

就这样。

如果您有任何疑问,请在下面发表评论。

弗朗西斯科

文章来源:https://dev.to/francescoxx/rust-crud-rest-api-3n45
PREV
使用 Docker 部署的 Next.js 应用程序 - 有意义吗?
NEXT
熟悉 Rust 的语法