跳至主要内容
Deno 2 终于来了 🎉️
了解更多
Express + Typescript + Deno

使用 Express、TypeScript 和 Deno 构建 REST API,第 2 部分

  • Tim Post

本教程的第一部分中,我们使用 Deno + Express 运行起来,无需任何配置,只需几分钟即可完成几个工作路由。如果你碰巧先看到了本教程的这一部分,我们建议你回去阅读第一部分并克隆伴随代码仓库。

在本节中,我们将添加一些测试和基准测试,以衡量代码更改对我们的 API 服务运行的影响。然后,我们将构建并启动 Docker 镜像。

从这一点开始,你可以从 伴随仓库中挑选你想要的内容,然后通过 GitOps 在自己的集群上部署你自己的 API,或者使用 Docker 镜像在任何支持 Docker 的主机上运行它。

Video Thumbnail 欢迎观看本文章的 视频演练

如果你想跳到代码部分,可以 在这里进行。

为我们的 Express 路由准备测试

我们希望确保我们的路由返回我们预期的结果,因此我们将使用 Deno 内置的测试工具来确保我们覆盖了所有内容。

我们创建一个名为 main_tests.ts 的文件(有关 Deno 如何搜索要运行的测试和基准测试的更多信息,请参阅 文档),其中包含以下代码

import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
const stagingUrl = Deno.env.get("STAGING_URL") || "https://127.0.0.1:3000";

Deno.test("Plural Users Route", async () => {
  const res = await fetch(`${stagingUrl}/users`);
  const response = await res.json();
  console.log(response);
  assertEquals(res.status, 200);
});

Deno.test("Single User Route & User Data", async () => {
  const res = await fetch(`${stagingUrl}/users/2`);
  const response = await res.json();
  assertEquals(res.status, 200);
  assertEquals(response.name, "Funmi");
});

了解更多有关 Deno 内置测试的信息。

我们的测试发出请求,等待响应代码,并且在单用户调用的情况下,验证特定调用的返回数据是否正确。一个非常简单的方法就是使用 fetch(),这就是我们使用异步函数的原因。

现在,我们可以添加基准测试!

测试验证了响应内容,但获取响应需要多长时间?为此,我们使用基准测试,这也是 Deno 内置工具链中非常方便的一部分。

与测试类似,Deno 会根据 _bench 后缀自动发现要运行的基准测试。我们创建 main_bench.ts,以便 Deno 知道这些是与主模块相对应的基准测试

const stagingUrl = Deno.env.get("STAGING_URL") || "https://127.0.0.1:3000";

Deno.bench("Single User", async () => {
  await fetch(`${stagingUrl}/users/2`);
});

Deno.bench("All Users", async () => {
  await fetch(`${stagingUrl}/users`);
});

这就算完成了!Deno 会处理运行测试、记录时间和报告统计信息。

请注意,我们关心的是请求从开始到结束所需的时间,而不是客户端解析响应所需的时间,因此基准测试故意有泄漏。

通过 Docker / GitOps 部署

如果你有现有的基础设施,可以从你的仓库/CI 服务器部署到容器,那么仓库中包含了所需的一切。第一个文件 docker-compose.yml 指定了详细信息

version: "3.9"

services:
  deno-express-api:
    container_name: deno-express-api
    image: deno
    restart: always
    build:
      context: .
      dockerfile: Dockerfile
      target: base
    ports:
      - "${PORT}:${PORT}"

此外,对于构建,我们还有我们的 Dockerfile

FROM denoland/deno:latest as base

WORKDIR /app

COPY . ./

RUN deno cache main.ts

CMD ["task", "start"]

此文件设置工作目录并将 /app 中的所有内容复制到根目录。命令 deno cache 使用 main.ts 作为入口点缓存所有依赖项。最后,它在 Docker 容器启动时运行 task start

请注意,我们只需要在命令行上定义 task start,因为我们已经在 deno.jsonc 中定义了此任务

{
  "tasks": {
    "dev": "deno run --allow-read --allow-net --allow-env --watch main.ts",
    "start": "deno run --allow-read --allow-net --allow-env main.ts"
  }
}

如果你的项目分支需要额外的权限,请记住在那里更新它们。现在,我们启动容器,以便我们可以对其运行测试和基准测试

$ docker compose up --build

Docker Compose, "docker compose up --build output"

你可以在你配置 Docker 用于存储它们的位置找到构建的容器;如果你不确定,请参阅 Docker 桌面应用程序。

运行测试

现在它已经运行起来,我们可以通过 deno test -A 运行我们的测试,并看到它们通过了

Deno test, "deno test -A output"

太好了!我们知道结果符合预期,但它们何时符合预期?我们通过 deno bench -A 运行我们的基准测试

Deno bench, "deno bench -A output"

迭代

那么,我们能用它做什么?好吧,我们在中间件中留下了很多详细的日志,因此我们在视频中做的一个练习是禁用控制台输出并重新运行基准测试,这确实显示出显著的改进。

如果你想一起学习,请打开 main.ts 并找到记录请求的中间件函数 reqLogger(),并将以下行注释掉

const reqLogger = function (req: Request, _res: Response, next: NextFunction) {
  // better to implement real logging for production
  // console.info(`${req.method} request to "${req.url}" by ${req.hostname}`);
  next();
};

然后,使用 deno task dev 启动开发服务,测试你的更改,并重新构建你的镜像或推送到 CI 服务器。

还值得一提的是,这些数字本身会根据你的本地设置而有所不同;我在录制视频时是在 MacBook Air 上运行它们。

你也可以使用 deno bench -A --json 以 JSON 格式导出基准测试数据,以便进行自动基准测试和绘图。

接下来做什么?

现在你已经准备好连接一个真实的数据存储,实现真实的路由、真实的测试和真实的基准测试。

要帮助你构建项目,请查看 express.Router

构建生产 API 还需要考虑 压缩、身份验证和速率限制选项,因此请花一些时间仔细阅读 Express 文档。

希望你发现这篇文章有用,并祝你的项目好运!

遇到问题?在 我们的 Discord 中寻求帮助!