使用 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: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} ...`);
});
现在我们准备就绪!
安全地启动服务器
让我们启动服务器
我们在开发 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" });
});
我们还可以清除 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!
处理程序已被删除(并且在链接的存储库中不存在)。
下一步是什么?
我们有一个出色的 REST API 起始点,代码少于 30 行。现在,您可以添加使用 app.post()
的 POST
处理程序、使用 app.put()
的 PUT 处理程序或您想要的任何其他方法。
在以后的文章中,我们将介绍如何使用 Deno 的测试运行器和基准测试工具,以便我们更放心地将我们的代码从概念验证转变为我们将在生产环境中信任的东西。之后,我们将以可以部署项目的方式结束本系列。
遇到困难?在我们的 Discord 中获取帮助!