使用 Express 在 Deno 上构建一个简单的 API
安装 Deno
入门
Deno是由 Node.js 的创始人 Ryan Dahl 创建的用于 JavaScript 和 TypeScript 的全新安全运行时。
与 Node 不同,Deno 不使用 NPM 或 Node 模块进行包管理。要使用第三方模块,您必须使用浏览器兼容的 URL,这些 URL 会解析为有效的 ES 模块——不支持 CommonJS!
Deno 的底层核心和标准库与 Node完全不同,这意味着很多库不再兼容,因为从文件系统操作到 HTTP 请求,所有功能都有了新的 API。
幸运的是,一些模块作者已经开始将 Node 模块转换为支持 TypeScript 和 Deno。如果您是 Node 模块的维护者,并且希望支持 Deno,我建议您了解一下@GarroneJoseph开发的Denoify项目。
本教程将介绍Opine Web 框架——一个基于 Express 移植到 Deno 平台的快速、极简的 Web 框架。它拥有几乎与 Express 完全相同的 API,并且是直接移植到 Deno 的,因此其内部机制与 Express 完全一致。
我们将构建一个简单的猫咪API,用于查询猫咪数据!🐱
安装 Deno
Deno 可以通过所有主流软件包安装程序以及官方安装脚本进行安装。以下是一些主要的安装方法:
Shell(Mac、Linux):
curl -fsSL https://deno.land/x/install/install.sh | sh
PowerShell(Windows):
iwr https://deno.land/x/install/install.ps1 -useb | iex
自制软件(Mac):
brew install deno
Chocolatey(Windows):
choco install deno
请前往Deno 安装页面查看其他安装方法和更多详细信息。
入门
安装好 Deno 后,您现在可以运行该deno命令了。使用deno help该命令可以浏览所有可用的命令。稍后我们将使用此命令来运行我们的 API 服务器。
让我们开始创建项目吧!在新目录中创建以下文件:
.
├── deps.ts
├── db.ts
└── server.ts
server.ts将包含我们的主要服务器代码,db.ts将包含我们的模拟数据库代码,deps.ts并将包含我们所有的依赖项和版本(有点像替代品package.json)。
导入我们的依赖项
我们在deps.ts文件中添加以下内容,以将所需的依赖项重新导出为所需的版本:
export { opine } from "https://deno.land/x/opine@0.21.2/mod.ts";
注意我们是通过 URL 导入的吗?没错,在 Deno 中,您可以从任何 URL 以及导出有效 ES 模块的相对或绝对文件路径导入模块。
这意味着您可以轻松地从网络上拉取任何代码,例如 gist、GitHub 代码,并且不再受限于已发布的版本——如果某个main分支(或任何其他功能分支!)上有您迫不及待想要尝试的内容,您可以直接导入它!
我们可以选择不使用依赖项deps.ts,直接将这些依赖项导入到服务器代码中,但是使用依赖项deps.ts文件在您想要升级依赖项时非常有用,因为您可以在一个地方更改版本,而不是在所有文件中更改(尤其是在大型项目中)!
编写我们的模拟数据库
我们db.ts创建一个简单的对象数组来表示我们的虚拟数据库:
export const database = [
{
id: "abys",
name: "Abyssinian",
url: "http://cfa.org/Breeds/BreedsAB/Abyssinian.aspx",
description:
"The Abyssinian is easy to care for, and a joy to have in your home. They’re affectionate cats and love both people and other animals.",
},
{
id: "aege",
name: "Aegean",
url: "http://www.vetstreet.com/cats/aegean-cat",
description:
"Native to the Greek islands known as the Cyclades in the Aegean Sea, these are natural cats, meaning they developed without humans getting involved in their breeding. As a breed, Aegean Cats are rare, although they are numerous on their home islands. They are generally friendly toward people and can be excellent cats for families with children.",
},
{
id: "abob",
name: "American Bobtail",
url: "http://cfa.org/Breeds/BreedsAB/AmericanBobtail.aspx",
description:
"American Bobtails are loving and incredibly intelligent cats possessing a distinctive wild appearance. They are extremely interactive cats that bond with their human family with great devotion.",
},
{
id: "acur",
name: "American Curl",
url: "http://cfa.org/Breeds/BreedsAB/AmericanCurl.aspx",
description:
"Distinguished by truly unique ears that curl back in a graceful arc, offering an alert, perky, happily surprised expression, they cause people to break out into a big smile when viewing their first Curl. Curls are very people-oriented, faithful, affectionate soulmates, adjusting remarkably fast to other pets, children, and new situations.",
},
{
id: "asho",
name: "American Shorthair",
url: "http://cfa.org/Breeds/BreedsAB/AmericanShorthair.aspx",
description:
"The American Shorthair is known for its longevity, robust health, good looks, sweet personality, and amiability with children, dogs, and other pets.",
},
{
id: "awir",
name: "American Wirehair",
url: "http://cfa.org/Breeds/BreedsAB/AmericanWirehair.aspx",
description:
"The American Wirehair tends to be a calm and tolerant cat who takes life as it comes. His favorite hobby is bird-watching from a sunny windowsill, and his hunting ability will stand you in good stead if insects enter the house.",
},
];
此数据来自TheCatAPI - Cats as a Service,每天都是周六猫日,这是一个免费使用的公共 API。
服务器设置和我们的第一个端点
现在让我们开始编写服务器吧!
import { opine, Router } from "./deps.ts";
import { database } from "./db.ts";
const app = opine();
const v1ApiRouter = Router();
// Add our /cats route to the v1 API router
// for retrieving a list of all the cats.
v1ApiRouter.get("/cats", (req, res) => {
res.setStatus(200).json({
success: "true",
data: database,
});
});
// Mount the v1 API router onto our server
// at the /api/v1 path.
app.use("/api/v1", v1ApiRouter);
const PORT = 3000;
// Start our server on the desired port.
app.listen(PORT);
console.log(`API server running on port ${PORT}`);
首先,我们从 Opine 模块导入opine并创建一个新的 Opine和一个路由器,该路由器将用于定义我们 API v1 的端点。Routerdeps.tsappv1ApiRouter
然后,我们使用该get()方法v1ApiRouter定义一个路由,用于处理/cats指向与第一个参数指定的路径匹配的端点的 GET 请求。第二个参数是一个函数,每次访问该端点时都会运行。该函数接受两个参数:`request` 和 `return` req(res您可以根据需要命名这些参数)。`request`req对象包含有关我们请求的信息,而 `return`res对象包含用于操作我们向请求该端点的用户发送的信息的属性和方法。
res.setStatus(200).json({
success: "true",
data: database,
});
我们使用该res.setStatus()方法将 HTTP 状态码设置为200(OK),以告知用户请求已成功。然后,我们使用res.json()链式调用的方法res.setStatus(),将包含猫咪数据库信息的 JSON 对象作为响应发送回用户。您不必链式调用这些方法,也可以编写如下代码:
res.setStatus(200)
res.json({
success: "true",
data: database,
});
/api/v1然后,我们使用以下方法将 v1 API 路由器添加到 Opine 应用的路径中app.use():
app.use("/api/v1", v1ApiRouter);
此命令现在会将所有 URL 以开头的请求路由/api/v1到我们的 v1 API 路由器。
最后,我们将 a 定义PORT为常量,并执行app.listen()命令启动服务器。
如果您熟悉 Express,您会发现这些命令几乎与您在为 Node 编写 Express 应用程序时使用的命令完全相同。为了便于比较,以下是相同的 Node 代码:
const express = require("express");
const { database } = require("./database");
const app = express();
const v1ApiRouter = express.Router();
v1ApiRouter.get("/cats", (req, res) => {
res.setStatus(200).json({
success: "true",
data: database,
});
});
app.use("/api/v1", v1ApiRouter);
const PORT = 3000;
app.listen(PORT, () => console.log(`API server running on port ${PORT}`));
就这样!让我们运行 API 服务器,看看会发生什么吧😄。
运行以下命令启动服务器,然后访问http://localhost:3000/api/v1/cats查看其响应内容:
deno run --allow-net ./server.ts
你应该会在浏览器中看到类似下面的响应。
你刚刚成功地用 Deno 编写了你的第一个 API!🎉
上传猫咪 API 端点
现在我们可以通过 API 获取猫咪的详细信息,但是还没有办法上传更多猫咪信息🐱。让我们现在就编写一个上传接口吧!
请对您的文件进行以下更改server.ts:
import { opine, Router } from "./deps.ts";
// *** NEW ***
import { getDatabase, addToDatabase } from "./db.ts";
const app = opine();
const v1ApiRouter = Router();
// Add our /cats route to the v1 API router
// for retrieving a list of all the cats.
v1ApiRouter.get("/cats", (req, res) => {
res.setStatus(200).json({
success: "true",
data: getDatabase(), // *** NEW ***
});
});
// *** NEW ***
// Add our /cats route to the v1 API router
// for uploading a cat to the database.
v1ApiRouter.put("/cats", (req, res) => {
const cat = req.parsedBody;
addToDatabase(cat);
res.sendStatus(201);
});
// *** NEW ***
// We use the Opine JSON body parser to allow
// us to parse the upload cat JSON object.
app.use(json());
// ... the remaining code from our previous example
这里有一些改动:
- 现在我们导入用于获取数据库数据和向数据库添加数据
getDatabase的addToDatabase方法db.ts。稍后我们将看到如何编写这些方法。 - 对于 GET
/cats请求处理程序,我们将dataJSON 响应中的属性更新为来自该方法getDatabase()。 - 然后,我们添加了一个新的路由处理程序
v1ApiRouter来处理发送到该端点的 PUT 请求/cats。该函数参数接收一个特殊属性req.parsedBody,该属性包含我们将要上传的 JSON 猫对象,并将其存储在变量中cat。然后,使用该方法将这个新的猫对象添加到数据库中addToDatabase()。最后,该函数调用res.sendStatus()方法并传入 HTTP 状态码201(Created),以通知用户他们已成功将新猫添加到数据库。 - 最后一点改动是新增了
app.use(json())一个函数。该函数来自 Opine 模块,它会返回一个中间件,该中间件会接收req.body每个请求的参数,如果请求是 JSON 格式,则会解析 JSON 并将其存储在req.parsedBody属性中。这使得我们能够通过新的 PUT 端点访问猫咪数据。
现在我们只需要将新的数据库方法添加到我们的代码中db.ts:
// ... the previous code from our example
export const getDatabase = () => database;
export const addToDatabase = (
cat: { id: string; name: string; url: string; description: string },
) => database.push(cat);
这里我们导出一个getDatabase()只返回database数组的方法,还导出addToDatabase()一个接受 cat 对象并执行database.push(cat)将 cat 对象添加到database数组的方法。
让我们再次运行服务器,看看能不能上传一只猫!
deno run --allow-net ./server.ts
下面这段代码片段演示了如何使用终端命令curl向新端点发出 PUT 请求/api/v1/cats,但您也可以使用任何请求发送平台,例如Postman。
curl -X PUT http://localhost:3000/api/v1/cats \
-d '{ "id": "top-cat", "name": "Top Cat", "url": "https://en.wikipedia.org/wiki/Top_Cat", "description": "Top Cat (or simply T.C.) is the yellow-furred, charismatic, and clever cat." }' \
-H 'Content-Type: application/json'
这里我们向端点发送一个 PUT 请求,并将包含我们猫咪Top Cat信息的 JSON 对象作为数据传递。我们还特意Content-Type在请求头中添加了 JSON 数据,以便 Opine 服务器知道请求体是 JSON 格式。
执行该命令,然后再次在浏览器中打开http://localhost:3000/api/v1/cats ...
瞧!🎉🎉 我们可以看到,新猫咪已经添加到数据库里了😄
下一步
我们刚刚了解了如何使用 Deno 在 Opine 服务器上实现用于从数据库检索和上传猫咪信息的 GET 和 PUT 端点。我们还了解了如何将路由器挂载到应用程序上,如何使用多个路由处理程序,以及如何添加 JSON 解析中间件来处理 JSON 请求体。
如果你想更进一步,不妨尝试以下方法之一:
- 添加一个 GET 请求
/cat/:id端点,用于通过猫的 ID 获取单个猫的信息。您可以参考Opine 示例了解如何实现通配符路由,或者查阅Opine 路由文档获取更多帮助。 - 添加一个 DELETE
/cat/:id端点,用于按 ID 删除猫。这应该与上面的端点非常相似,难点在于确保从数据库中删除正确的猫! - 给你的接口添加一些验证。如果用户尝试获取或删除一只不存在的猫怎么办?如果用户尝试上传一只缺少属性的猫怎么办?为什么不添加一些
if语句来确保请求有效,如果无效,则返回400“错误请求”或404“未找到”呢?
就这些啦!很想听听你们的想法,以及你们使用Deno的进展——欢迎在下方留言提问!我会尽力帮忙的😄
文章来源:https://dev.to/craigmorten/building-a-simple-api-on-deno-with-express-4mlj

