使用 Express、TypeScript 和 Deno 构建 REST API
对于任何想要开始使用 TypeScript 和 Express 构建 REST API 的人来说,有很多非常棒的教程。这些教程虽然很棒,但有两个缺点
它们要求您安装和配置 TypeScript,并提供完成该操作所需的一切。这可能很耗时,并且容易让人感到沮丧,尤其是对于新手开发者来说。
它们没有解决主动限制不受信任的代码的需求;这并不奇怪,因为大多数工具都不支持这种需求。
这就是我们创建本教程的原因。使用 Deno,您无需配置 TypeScript,因此您可以使用最少的依赖项快速上手。
欢迎观看本文章的 视频演练。
如果您想直接跳到代码,您可以从 这里开始。
设置 Express 及其类型
让我们创建一个 main.ts
文件,它将包含我们 API 的逻辑。
在这个文件中,让我们通过 npm
规范导入 Express。
import express, { NextFunction, Request, Response } from "npm:[email protected]";
这将获取 express,但不会获取类型定义。让我们通过添加以下注释来导入类型定义
// @deno-types="npm:@types/express@4"
import express, { NextFunction, Request, Response } from "npm:[email protected]";
接下来,我们需要定义一种与 Express 应用程序接口交互的方式,并且需要定义一个运行端口,我们将在环境中获取该端口
const app = express();
const port = Number(Deno.env.get("PORT")) || 3000;
让我们定义一个测试路由,它将在接收 GET 请求时发出问候,我们现在将将其作为默认基本路由
app.get("/", (_req, res) => {
res.status(200).send("Hello from Deno and Express!");
});
现在我们已经构建了简单的逻辑,我们只需要它监听并开始处理请求!为此,我们将使用 .listen()
,如所示
app.listen(port, () => {
console.log(`Listening on ${port} ...`);
});
现在,我们准备好了!
安全启动服务器
让我们启动我们的服务器
在开发 API 时,我们必须引入各种代码,从地理信息、AI、广告服务器,以及其他必须整合在一起才能生成所需内容的输入。当然,我们不希望 Express 引入漏洞,但 Express 只是您需要使用才能构建某些东西的堆栈的一部分。
如果它请求访问系统信息、高分辨率计时器或目录之外的访问权限,那将是一个危险信号。您可以通过多种方式指定权限,包括脚本中的 shebang。
此时,我们有一个正在运行的 API 服务,我们可以使用 curl 查询它
我们现在可以确定框架是否正常工作,因此我们对安装和所有其他内容都充满信心。但是,它还不是一个很好的工作环境,所以让我们设置 deno.jsonc
文件以定义一些辅助脚本
它与 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 dev
和 deno task start
。
添加日志记录
接下来我们需要某种日志记录功能,以便我们在构建请求时进行故障排除,这将很好地介绍 Express 中的中间件概念。
中间件是一个函数,可以读取甚至修改 req
和 res
对象。我们使用中间件来执行各种操作,从日志记录到注入标头,甚至速率限制和检查身份验证。中间件必须在完成时执行以下两项操作之一
- 如果适用,它必须关闭连接,并返回响应,或者
- 它必须调用
next()
,它告诉 Express 现在是将对象传递给下一个中间件函数的时候了
中间件接受 3 个参数:req
和 res
,正如您所料,还有 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" });
});
我们也可以清除问候世界默认路由,这将为我们提供一个很好的 API 起点
// @deno-types="npm:@types/express@4"
import express, { NextFunction, Request, Response } from "npm:[email protected]";
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 提供了一个很好的起点。现在,您可以使用 app.post()
添加 POST
处理程序,使用 app.put()
添加 PUT 处理程序,或者添加您想要的任何其他方法。
在以后的文章中,我们将介绍如何使用 Deno 的测试运行器和基准测试工具,以便我们更放心地将代码从概念验证转变为我们在生产环境中信任的内容。然后,我们将在这个系列的最后介绍如何部署项目。
遇到问题?在 我们的 Discord 中寻求帮助!