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

使用 Express、TypeScript 和 Deno 构建 REST API

  • 蒂姆·波斯特 (Tim Post)

市面上有许多非常棒的教程,可帮助您使用 TypeScriptExpress 构建 REST API。然而,这些教程尽管优秀,却存在两个缺点:

  1. 它们要求您安装和配置 TypeScript,并包含所有必需的依赖。这可能非常耗时,并且会让人感到沮丧,尤其是对新开发者而言。

  2. 它们没有主动解决将不可信代码隔离的需求;这并不奇怪,因为大多数工具都不支持这一点。

这就是我们创建本教程的原因。使用 Deno,您无需配置 TypeScript,因此可以以最少的依赖项快速启动和运行。

视频缩略图 欢迎观看本文的视频讲解

如果您想直接查看代码,可以点击此处

设置 Express 及其类型

我们来创建 main.ts 文件,它将包含我们 API 的逻辑。

在此文件中,我们通过 npm 指定符导入 Express。

import express, { NextFunction, Request, Response } from "npm:express@4.18.2";

这使我们获得了 Express,但没有类型定义。我们通过添加此注释来导入类型定义

// @deno-types="npm:@types/express@4"
import express, { NextFunction, Request, Response } from "npm:express@4.18.2";

接下来,我们需要定义一种与 Express 应用程序接口交互的方式,并且需要为其定义一个运行端口,该端口将从环境中获取

const app = express();
const port = Number(Deno.env.get("PORT")) || 3000;

我们来定义一个测试路由,它在收到 GET 请求时会返回 hello,我们暂时将其设为默认的基础路由

app.get("/", (_req, res) => {
  res.status(200).send("Hello from Deno and Express!");
});

现在我们已经构建了简单的逻辑,只需要让它监听并开始处理请求!为此,我们将使用 .listen(),如下所示

app.listen(port, () => {
  console.log(`Listening on ${port} ...`);
});

现在我们准备好了!

安全地启动服务器

我们来启动服务器

Deno Permissions, "Deno Requesting Permissions"

在开发 API 时,我们不得不引入各种代码,从地理信息、人工智能、广告服务器到任何其他必须协同工作以生成所需内容的输入。当然,我们不认为 Express 会引入漏洞,但 Express 只是您需要构建某些东西的堆栈中的一部分。

如果它请求访问系统信息、高精度计时器或目录外部的访问,那将是一个危险信号。您可以通过多种方式指定权限,包括脚本中的 shebang。

至此,我们有了一个正在运行的 API 服务,可以使用 curl 进行查询

Deno Express Hello World, "Deno Saying Hello Through Express"

我们现在确定框架工作正常,因此对安装和其他一切都充满信心。但是,这还不是一个理想的工作环境,所以我们来设置 deno.jsonc 文件来定义一些辅助脚本

Deno Config Showing Tasks, "Deno.jsonc in vscode"

这类似于 package.json 脚本的工作方式(实际上,Deno 甚至可以使用 package.json 脚本,但建议使用 deno.jsonc),我们为一个任务用于开发,另一个任务用于启动服务器,而无需监听和在更改时重新加载。

查看 deno task 的输出,我们可以确认有两个可用脚本

$ deno task
Available tasks:
- dev
    deno run --allow-read --allow-net --allow-env --watch main.ts
- start
    deno run --allow-read --allow-net --allow-env main.ts

我们可以分别使用 deno task devdeno task start

添加日志

接下来我们需要的是某种日志功能,以便我们在构建请求时能够进行故障排除,这也是一个很好的机会来介绍 Express 中的中间件概念。

中间件是一个函数,可以读取甚至修改 reqres 对象。我们使用中间件来完成从日志记录到注入头,甚至限速和验证身份等所有事情。中间件完成时必须做以下两件事之一

  • 如果合适,它必须通过响应关闭连接,或者
  • 它必须调用 next(),这会告诉 Express 是时候将对象传递给下一个中间件函数了

中间件接受 3 个参数:reqres(如您所料),以及 next,它指向下一个合适的中间件函数(或将控制权返回给处理函数)。

与其在我们编写的每个处理程序中都使用 console.log(),不如我们将第一个中间件函数定义为日志记录器,并告诉 Express 我们要使用它。在 main.ts

const reqLogger = function (req, _res, next) {
  console.info(`${req.method} request to "${req.url}" by ${req.hostname}`);
  next();
};

您可以拥有任意数量的中间件,并以适合您的方式组织它们。请记住,您的响应速度取决于您的中间件链将控制权交还给框架的速度。中间件按照框架获知它们的顺序执行。

生成数据

现在我们已经做好充分准备开始开发了。运行 ./generate_data.ts 命令(如果 shebang 对您不起作用,请运行 deno run -A ./generate_data.ts),这将在 data_blob.json 中生成一些模拟用户数据,我们可以通过 Deno 的导入类型断言像任何其他只读数据存储一样安全地使用它

import demoData from "./data_blob.json" with { type: "json" };

现在我们可以在处理程序中访问 demoData.users,所以我们来编写两个处理程序

  • 一个 /users,它返回用户对象的全部内容,以及
  • 一个额外的动态路由,允许我们通过 ID 查找单个用户
app.get("/users", (_req, res) => {
  res.status(200).json(demoData.users);
});

app.get("/users/:id", (req, res) => {
  const idx = Number(req.params.id);
  for (const user of demoData.users) {
    if (user.id === idx) {
      res.status(200).json(user);
    }
  }
  res.status(400).json({ msg: "User not found" });
});

我们还可以删除“Hello, world!”默认路由,这样我们就得到了一个不错的 API 起点

// @deno-types="npm:@types/express@4"
import express, { NextFunction, Request, Response } from "npm:express@4.18.2";
import demoData from "./data_blob.json" with { type: "json" };

const app = express();
const port = Number(Deno.env.get("PORT")) || 3000;

const reqLogger = function (req, _res, next) {
  console.info(`${req.method} request to "${req.url}" by ${req.hostname}`);
  next();
};

app.use(reqLogger);

app.get("/users", (_req, res) => {
  res.status(200).json(demoData.users);
});

app.get("/users/:id", (req, res) => {
  const idx = Number(req.params.id);
  for (const user of demoData.users) {
    if (user.id === idx) {
      res.status(200).json(user);
    }
  }
  res.status(400).json({ msg: "User not found" });
});

app.listen(port, () => {
  console.log(`Listening on ${port} ...`);
});

请注意,/ 上的 Hello, world! 处理程序已被删除(并且在链接的仓库中不存在)。

接下来是什么?

我们用不到 30 行代码就为 REST API 有了一个很好的起点。现在,您可以添加一个 POST 处理程序(使用 app.post())、一个 PUT 处理程序(使用 app.put()),或者您想要的任何其他方法。

在未来的文章中,我们将介绍如何使用 Deno 的测试运行器和基准测试工具,这样我们就可以更放心地将代码从概念验证阶段推进到可在生产环境中信任的程度。之后,我们将以项目部署方式来结束本系列。

遇到困难?在我们的 Discord 中寻求帮助!