跳到主要内容
Express + Typescript + Deno

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

  • Tim Post

本教程的第一部分中,我们已经在几分钟内使用 Deno + Express 启动并运行,无需任何配置,并设置了几个可用的路由。如果您首先偶然看到了本教程的这一部分,我们建议您返回阅读第一部分并克隆配套的代码仓库。

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

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

视频缩略图 欢迎观看此帖子的视频演练

如果您想跳到代码部分,可以点击此处

为我们的 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(),这就是我们使用 async 函数的原因。

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

测试验证响应包含什么,但是获得响应需要多长时间?为此,我们使用基准测试,这也是 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 中获取帮助!