发布于 2026-01-05 4 阅读
0

使用 Deno 编写你的第一个 API

使用 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 框架可供选择,例如AlosaurOakDeno-ExpressPOGO和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 });
Enter fullscreen mode Exit fullscreen mode

如果您之前使用过 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
Enter fullscreen mode Exit fullscreen mode

现在,如果您访问localhost:8000,您应该会在屏幕上看到“Hello, Abc!”。

太好了!接下来我们添加数据库。

数据库(MongoDB)

我们将从环境变量中获取数据库 URL 和名称。因此,请在您的.env文件中添加以下内容。

DB_NAME=deno_dogs
DB_HOST_URL=mongodb://localhost:27017
Enter fullscreen mode Exit fullscreen mode

现在将以下内容添加到您的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;
Enter fullscreen mode Exit fullscreen mode

让我们来分析一下我们编写的代码。幸运的是,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);
  }
};
Enter fullscreen mode Exit fullscreen mode

好了,这里涉及的内容很多。首先,我们从 Abc 模块和我们自己的文件中导入了相关模块HandlerFuncContext然后,我们调用函数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 });
Enter fullscreen mode Exit fullscreen mode

如您所见,我们已经导入了所有控制器函数,并为每个函数分配了路由和HTTP方法。就这么简单。

我们的 REST API 编写工作已经完成。现在只需要运行它!为此,请在终端中输入以下命令:

deno run --allow-write --allow-read --allow-plugin --allow-net --allow-env --unstable server.ts
Enter fullscreen mode Exit fullscreen mode

这次我们新增了一些标志。这些--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
}
Enter fullscreen mode Exit fullscreen mode

向同一个网址发送 GET 请求即可查看你的狗狗!同样地,尝试所有其他路由。

好了!现在你知道如何用 Deno 编写 REST API 了。

这是代码的GitHub仓库。

结论

由于目前存在一些漏洞,而且该项目还没有明确的行为准则,因此我不建议现在将其用于生产环境。行为准则是​​任何开源项目必不可少的组成部分。不过,该项目的开发进展非常迅速,绝对值得密切关注!

如有任何疑问,请联系我的社交媒体GitHub

文章来源:https://dev.to/gdsckiitdev/write-your-first-api-with-deno-1llh