使用 Deno 编写你的第一个 API
如果你错过了,Deno v1.0 已经发布了!
但Deno是什么?
Deno 是一个简单、现代且安全的 JavaScript 和 TypeScript 运行时环境,它使用 V8 引擎,并用 Rust 语言编写。
这是根据官方网站的说法。
Node.js(流行的服务器端 JavaScript 运行时)的创始人 Ryan Dahl 在 2018 年 JSConf EU 大会上发表了题为“我对 Node.js 的十件遗憾”的演讲,正式发布了 Deno。Deno 与 Node.js 非常相似,但它在很多方面都进行了改进,因为它旨在成为 Node.js 的更佳实现。它拥有众多强大的功能,例如默认安全、默认 TypeScript、ES 模块以及类似 Golang 的包管理。
如果你在推特上,你可能已经看到大量“x 年 Deno 经验”和“node、deno、oden、完成……”之类的玩笑。
入门
好了,就说这么多吧,让我们开始玩Deno吧。
我们将构建一个非常简单的 REST API,它允许我们对一个包含狗狗数据的数据库执行 CRUD 操作!
请确保您已正确安装Deno。
我们将使用Abc deno Web 框架以及MongoDB。我们还会使用Denv来管理环境变量。请注意,还有很多其他的 Web 框架可供选择,例如Alosaur、Oak、Deno-Express、POGO和Servest,但由于 deno 还比较新,而且我目前还没有特别的偏好,所以我暂时使用 deno。
创建一个新目录,并在其中创建一个名为 `.router` 的文件server.ts。这将是我们的主文件,其中包含我们的路由器配置。我们还将导入 Denv 和 Abc 并初始化应用程序。
import { Application, Context } from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts";
import "https://deno.land/x/denv/mod.ts";
const app = new Application();
app
.get("/hello", (c: Context) => {
return "Hello, Abc!";
})
.start({ port: 8000 });
如果您之前使用过 Node.js,那么这段代码看起来应该很熟悉。首先,我们导入了ApplicationAbcContext模块。我们实际上是在初始化一个新的 Abc 应用程序,然后定义了一个路由,/hello其中包含一个回调函数,该函数会返回“Hello, Abc!”。该start方法指示应用程序开始监听 8000 端口的请求。这里没有请求和响应,只有一个参数,c其类型为 ` Context.`。让我们看看它的实际运行效果。要运行我们的文件,我们需要使用 `deno.js` 命令,deno run server.ts但是如果您直接运行该文件,将会收到一堆错误。这是因为 Deno 默认是安全的,它不允许应用程序以任何方式访问您的系统。为了允许它访问,我们需要添加 `--password` 标志--allow-read以允许 Denv 读取我们的.env文件,并--allow-net添加 `--port` 标志以允许 Abc 访问我们的端口。因此,命令应该是:
deno run --allow-read --allow-net server.ts
现在,如果您访问localhost:8000,您应该会在屏幕上看到“Hello, Abc!”。
太好了!接下来我们添加数据库。
数据库(MongoDB)
我们将从环境变量中获取数据库 URL 和名称。因此,请在您的.env文件中添加以下内容。
DB_NAME=deno_dogs
DB_HOST_URL=mongodb://localhost:27017
现在将以下内容添加到您的config/db.ts文件中
import { init, MongoClient } from "https://deno.land/x/mongo@v0.6.0/mod.ts";
// Initialize the plugin
await init();
class Database {
public client: MongoClient;
constructor(public dbName: string, public url: string) {
this.dbName = dbName;
this.url = url;
this.client = {} as MongoClient;
}
connect() {
const client = new MongoClient();
client.connectWithUri(this.url);
this.client = client;
}
get getDatabase() {
return this.client.database(this.dbName);
}
}
const dbName = Deno.env.get("DB_NAME") || "deno_dogs";
const dbHostUrl = Deno.env.get("DB_HOST_URL") || "mongodb://localhost:27017";
const db = new Database(dbName, dbHostUrl);
db.connect();
export default db;
让我们来分析一下我们编写的代码。幸运的是,Deno 支持 MongoDB,因此我们可以导入该模块。这将下载一个 MongoDB 插件。该init()方法初始化插件,我们定义我们的Database类。该类有一个构造函数,它接受数据库的 URL 和名称作为参数。该connect()方法连接到 MongoDB 实例,该getDatabase()方法是一个 getter 函数。在文件末尾,我们定义了该类的一个实例,db并使用从文件中获取的 dbName 和 dbHostUrl 对其进行初始化.env。我们还调用了该connect()方法并导出了该类db。
太棒了!现在让我们编写控制器,以便对数据库执行 CRUD 操作。
控制器
在文件中controllers/dogs.ts添加以下内容。
import {
HandlerFunc,
Context,
} from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts";
import db from "../config/db.ts";
// DB collection made
const database = db.getDatabase;
const dogs = database.collection("dogs");
// Dog type defined
interface Dog {
_id: {
$oid: string;
};
name: string;
breed: string;
age: string;
}
export const createDog: HandlerFunc = async (c: Context) => {
try {
const body = await (c.body());
if (!Object.keys(body).length) {
return c.string("Request can't be empty", 400);
}
const { name, breed, age } = body;
const insertedDog = await dogs.insertOne({
name,
breed,
age,
});
return c.json(insertedDog, 201);
} catch (error) {
return c.json(error, 500);
}
};
export const fetchAllDogs: HandlerFunc = async (c: Context) => {
try {
const fetchedDogs: Dog[] = await dogs.find();
if (fetchedDogs) {
const fetchedDogsList = fetchedDogs.length
? fetchedDogs.map((dog) => {
const { _id: { $oid }, name, breed, age } = dog;
return { id: $oid, name, breed, age };
})
: [];
return c.json(fetchedDogsList, 200);
}
} catch (error) {
return c.json(error, 500);
}
};
export const fetchOneDog: HandlerFunc = async (c: Context) => {
try {
const { id } = c.params as { id: string };
const fetchedDog = await dogs.findOne({ _id: { "$oid": id } });
if (fetchedDog) {
const { _id: { $oid }, name, breed, age } = fetchedDog;
return c.json({ id: $oid, name, breed, age }, 200);
}
return c.string("Dog not found", 404);
} catch (error) {
return c.json(error, 500);
}
};
export const updateDog: HandlerFunc = async (c: Context) => {
try {
const { id } = c.params as { id: string };
const body = await (c.body()) as {
name?: string;
breed?: string;
age?: string;
};
if (!Object.keys(body).length) {
return c.string("Request can't be empty", 400);
}
const fetchedDog = await dogs.findOne({ _id: { "$oid": id } });
if (fetchedDog) {
const { matchedCount } = await dogs.updateOne(
{ _id: { "$oid": id } },
{ $set: body },
);
if (matchedCount) {
return c.string("Dog updated successfully!", 204);
}
return c.string("Unable to update dog");
}
return c.string("Dog not found", 404);
} catch (error) {
return c.json(error, 500);
}
};
export const deleteDog: HandlerFunc = async (c: Context) => {
try {
const { id } = c.params as { id: string };
const fetchedDog = await dogs.findOne({ _id: { "$oid": id } });
if (fetchedDog) {
const deleteCount = await dogs.deleteOne({ _id: { "$oid": id } });
if (deleteCount) {
return c.string("Dog deleted successfully!", 204);
}
return c.string("Unable to delete dog");
}
return c.string("Dog not found", 404);
} catch (error) {
return c.json(error, 500);
}
};
好了,这里涉及的内容很多。首先,我们从 Abc 模块和我们自己的文件中导入了相关模块HandlerFunc。Context然后,我们调用函数db获取“deno_dogs”数据库,并在其中定义一个名为“dogs”的集合。接下来,我们定义一个接口,该接口包含 name、breed 和 age 三个属性。完成这些准备工作后,我们继续来看函数。config/db.tsgetDatabase()Dog
每个函数都有一个类型为 ` HandlerFunc<string>` 的参数,c该参数的类型为 `<string>` Context。这使得我们可以将此函数用作路由的回调函数。所有函数几乎都类似,因此无需过多解释。c.body()对于 `<string>` 和 `<string>`,我们使用 `<string>` 来访问请求体。在上述所有方法中,我们都通过 `<string>` 或 `<string>` 返回 JSON 对象或字符串,并在返回语句中包含 HTTP 状态码。对于 `<string>`createDog和` <string>`,我们通过 `<string>` 访问 URL 参数。updateDogc.json()c.string()c.paramsfetchOneDog, updateDogdeleteDog
我们还使用该对象在函数中通过诸如 `get( )` 和 `get ()` 之dogs类的方法来操作集合。所有这些方法都用 try-catch 块包裹起来以进行错误处理。.insertOne({}), .find({}), .findOne({}), .updateOne({})deleteOne({})
现在控制器已经定义好了,我们可以继续定义路由了。
路线
请在server.ts文件中写入以下内容。
import { Application } from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts";
import "https://deno.land/x/denv/mod.ts";
import {
createDog,
fetchAllDogs,
fetchOneDog,
updateDog,
deleteDog,
} from "./controllers/dogs.ts";
const app = new Application();
app
.get("/dogs", fetchAllDogs)
.post("/dogs", createDog)
.get("/dogs/:id", fetchOneDog)
.put("/dogs/:id", updateDog)
.delete("/dogs/:id", deleteDog)
.start({ port: 8000 });
如您所见,我们已经导入了所有控制器函数,并为每个函数分配了路由和HTTP方法。就这么简单。
我们的 REST API 编写工作已经完成。现在只需要运行它!为此,请在终端中输入以下命令:
deno run --allow-write --allow-read --allow-plugin --allow-net --allow-env --unstable server.ts
这次我们新增了一些标志。这些--allow-read/write标志用于 Denv 和 MongoDB,因为它们需要对文件系统进行读写访问。其中一个--allow-plugin标志允许使用 MongoDB 插件,另一个标志--allow-env允许使用环境变量。
许多 Deno API 尚不稳定,因此其中一些被标记为不稳定版本。要使用这些“不稳定”的 API,我们需要添加相应的--unstable标志。
使用Postman之类的工具,向localhost:8000/dogs发送 POST 请求,请求体内容如下:
{
"name": "Cheddar",
"breed": "Corgi",
"age": 11
}
向同一个网址发送 GET 请求即可查看你的狗狗!同样地,尝试所有其他路由。
好了!现在你知道如何用 Deno 编写 REST API 了。
这是代码的GitHub仓库。
结论
由于目前存在一些漏洞,而且该项目还没有明确的行为准则,因此我不建议现在将其用于生产环境。行为准则是任何开源项目必不可少的组成部分。不过,该项目的开发进展非常迅速,绝对值得密切关注!
文章来源:https://dev.to/gdsckiitdev/write-your-first-api-with-deno-1llh