跳到主要内容
Deno 2.4 发布,带来 deno bundle、字节/文本导入、OTel 稳定版等功能
了解更多
Express + Typescript + Deno

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

  • 蒂姆·波斯特 (Tim Post)

本教程的第一部分中,我们仅用几分钟时间就让 Deno + Express 运行起来,无需任何配置,并有几个可用的路由。如果您首先看到了本教程的一部分,我们建议您返回阅读第一部分并克隆配套代码仓库。

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

从这里开始,您可以从配套仓库中选择您想要的部分,然后通过GitOps在您自己的集群上部署您的 API,或者使用 Docker 镜像在任何支持 Docker 的主机上运行它。

Video Thumbnail 您可以观看本文章的视频演示

如果您想直接查看代码,可以在这里找到。

为 Express 路由设置测试

我们想确保我们的路由返回我们期望的结果,因此我们将使用 Deno 内置的测试工具来确保覆盖全面。

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

import { assertEquals } from "https://deno.land/std@0.182.0/testing/asserts.ts";
const stagingUrl = Deno.env.get("STAGING_URL") || "https://: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://: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 中获得帮助!