我每周都会探索一些新的项目,但很少会有像 Deno 这样吸引我的注意力的。

在本文中,我会让你快速了解 Deno,并把它与 Node.js 进行比较,以此构建你的第一个 REST API。

什么是Deno?

Deno 就像 Node,但是在很多方面都得到了深入的改善。先从 Deno 功能列表开始:

  • 它基于 JavaScript 语言的现代功能
  • 它有全面的标准库
  • 它以 TypeScript 为核心,以许多不同的方式带来了巨大的优势,包括一流的 TypeScript 支持(你不必单独编译 TypeScript,它由 Deno 自动完成)
  • 它包含 ES 模块
  • 没有包管理器
  • 它有一流的 await
  • 内置测试功能
  • 它尽可能地与浏览器保持兼容,例如提供内置的 fetch 和全局 window 对象

The Deno Handbook: A TypeScript Runtime Tutorial with Code Examples

我们将在本指南中探索所有这些功能。

在你开始使用 Deno 并了解了其功能之后,Node.js 看起来就像是“旧的”东西。

特别是因为 Node.js API 是基于回调的,它是在 promise 和 async/await之前编写的。 Node 中没有可用于修改的余地,这种修改的代价将会是巨大的。所以我们只能用回调或大量的 API 调用。

Node.js 非常棒,并将继续成为 JavaScript 世界中事实上的标准。但是我想我们会逐渐看到 Deno 由于其一流的 TypeScript 支持和现代标准库而越来越被广泛的采用。

由于没有向后兼容性的报复,所以 Deno 可以用现代技术编写所有的东西。当然我们无法保证十年之内在 Deno 身上也会发生同样的事情,并且会出现一项新技术,但这是目前的现实。

为什么是 Deno?为什么是现在?

大约2年前,Node.js 的原始创建者 Ryan Dahl 在 JSConf EU 上宣布了 Deno(油管上的演讲视频),这非常有趣,如果你经常用 Node.js 和 JavaScript,那么它是必看的。

每个项目经理都必须做出决定。 Ryan 对 Node 中的一些早期决定感到遗憾。此外,技术也在不断发展,如今的 JavaScript 与 2009 年 Node 创立时的语言已经完全不同。比如现代的 ES6/2016/2017 功能等。

所以他开始了一个新项目,用来创建第二波基于 JavaScript 的服务器端程序。

我现在而不是两年前写本文的原因是,技术需要大量时间才能成熟。我们终于达到了 Deno 1.0(1.0 在2020年5月13日发布),这是 Deno 正式宣布稳定的第一版。

这似乎只是一个数字,但 1.0 表示直到 Deno 2.0 才会有重大突破。当你采用一种新技术时,这很重要——你不想学习某些东西,因为它改变得太快。

你应该学习Deno吗?

这是一个大问题。

学习诸如 Deno 之类的新东西需要很大的努力。我的建议是,如果你现在开始使用服务器端 JS,并且还不了解 Node,并且从未编写过任何 TypeScript 代码,那么就从 Node 开始。没有人因为选择 Node.js 而被解雇。

但是,如果你喜欢 TypeScript,想要在任何地方使用 await,但不想依赖项目中庞大的 npm 包,那么 Deno 可能就是你想要的。

它会取代 Node.js 吗?

答案是否定的。Node.js 是一项庞大的、完善的、获得了良好支持的技术,它将会持续数十年。

一流的TypeScript支持

Deno 用 Rust 和 TypeScript 编写,这两种语言今天正在迅速发展。

特别是使用 TypeScript 意味着即使我们选择用纯 JavaScript编写代码,也可以获得 TypeScript 的很多好处。

使用 Deno 运行 TypeScript 代码不需要编译步骤——Deno 会自动为你执行这一步骤。

你不会被迫使用 TypeScript 编写代码,但是 Deno 的核心是用 TypeScript 编写的这一事实是明显的。

首先越来越多的 JavaScript 程序员开始喜欢 TypeScript。

其次,你使用的工具可以推断出许多有关用 TypeScript 编写的软件的信息,例如 Deno。

这意味着,当我们用 VS Code 进行编码时(由于二者都是在 MicroSoft 上开发的,因此与 TypeScript 紧密集成),可以在编写代码时获得类型检查和高级 IntelliSense 功能。换句话说,编辑器能够以非常有用的方式帮助我们。

与 Node.js 的异同

由于 Deno 基本上是 Node.js 的替代品,所以直接对两者比较非常有用。

相似之处:

  • 两者都是基于 V8 Chromium Engine 开发的
  • 两者都非常适合用 JavaScript 开发服务器端

差异:

  • Node 用 C++ 和 JavaScript 编写。 Deno 用 Rust 和 TypeScript 编写。
  • Node 有一个名为 npm 的官方包管理器。 Deno 没有,而是让你从 URL 导入任何 ES 模块。
  • Node 使用 CommonJS 语法导入 pacakges。 Deno 使用官方的 ES 模块。
  • Deno 在其所有 API 和标准库中使用现代 ECMAScript 功能,而 Node.js 使用基于回调的标准库,并且没有计划对其进行升级。
  • Deno 通过权限提供了一个沙箱安全层。程序只能访问由用户设置为可执行文件的权限作为标志。 Node.js 程序可以访问用户有权访问的任何内容。
  • Deno 长期以来一直在考虑将程序编译成可执行文件的可能性,而这种可执行文件可以在没有外部依赖项的情况下运行,就像 Go 一样,不过现在还没有正式提上日程。这将改变游戏规则。

没有包管理器

没有包管理器并且必须依靠 URL 来承载和导入包有利有弊。我真的很喜欢 pros:它非常灵活,我们可以创建软件包而无需将其发布到 npm 这样的存储库中。

我认为会有某种包管理器出现,但是还没有官方的消息。

Deno 网站为第三方软件包提供代码托管(并通过 URL 分发):https://deno.land/x/

安装 Deno

聊的够多了!下面开始安装 Deno。

在 Mac 上最简单的方法是用 Homebrew

brew install deno

img

一旦完成,你将可以访问 deno 命令。下面是你可以用 deno --help 获得的帮助:

flavio@mbp~> deno --help
deno 0.42.0
A secure JavaScript and TypeScript runtime

Docs: https://deno.land/std/manual.md
Modules: https://deno.land/std/ https://deno.land/x/
Bugs: https://github.com/denoland/deno/issues

To start the REPL, supply no arguments:
  deno

To execute a script:
  deno run https://deno.land/std/examples/welcome.ts
  deno https://deno.land/std/examples/welcome.ts

To evaluate code in the shell:
  deno eval "console.log(30933 + 404)"

Run 'deno help run' for 'run'-specific flags.

USAGE:
    deno [OPTIONS] [SUBCOMMAND]

OPTIONS:
    -h, --help
            Prints help information

    -L, --log-level <log-level>
            Set log level [possible values: debug, info]

    -q, --quiet
            Suppress diagnostic output
            By default, subcommands print human-readable diagnostic messages to stderr.
            If the flag is set, restrict these messages to errors.
    -V, --version
            Prints version information


SUBCOMMANDS:
    bundle         Bundle module and dependencies into single file
    cache          Cache the dependencies
    completions    Generate shell completions
    doc            Show documentation for a module
    eval           Eval script
    fmt            Format source files
    help           Prints this message or the help of the given subcommand(s)
    info           Show info about cache or info related to source file
    install        Install script as an executable
    repl           Read Eval Print Loop
    run            Run a program given a filename or url to the module
    test           Run tests
    types          Print runtime TypeScript declarations
    upgrade        Upgrade deno executable to newest version

ENVIRONMENT VARIABLES:
    DENO_DIR             Set deno's base directory (defaults to $HOME/.deno)
    DENO_INSTALL_ROOT    Set deno install's output directory
                         (defaults to $HOME/.deno/bin)
    NO_COLOR             Set to disable color
    HTTP_PROXY           Proxy address for HTTP requests
                         (module downloads, fetch)
    HTTPS_PROXY          Same but for HTTPS

Deno 命令

请注意帮助中的 SUBCOMMANDS 部分,其中列出了我们可以运行的所有命令。都有哪些子命令呢?

  • bundle 把模块和项目的依赖关系打包到单个文件中
  • cache 缓存依赖项
  • completions 生成 shell 补全
  • doc 显示模块的文档
  • eval 用来评估一段代码,例如 deno eval "console.log(1 + 2)"
  • fmt 内置的代码格式化程序(类似于 Go 中的 gofmt
  • help 打印此消息或给定子命令的帮助
  • info显示有关缓存的信息或与源文件有关的信息
  • install 把脚本作为可执行文件进行安装
  • repl 读取评估打印循环(默认)
  • run 运行为模块指定文件名或 URL 的程序
  • test 运行测试
  • types 打印运行时 TypeScript 声明
  • upgrade upgrade deno to the newest version
  • upgrade 升级到最新版本的 deno

可以运行 deno help 来获取命令的特定其他说明,例如 deno run --help

就像帮助所说的那样,我们可以用这个命令使 deno 来启动REPL(Read-Execute-Print-Loop),而无需任何其他操作。

img

这与运行 deno repl 相同。

这个命令的一种更常见的使用方法是执行包含在 TypeScript 文件中的 Deno 程序。

你可以同时运行 TypeScript(.ts)文件与 JavaScript(.js)文件。

如果你不熟悉 TypeScript,请不要担心:尽管 Deno 是用 TypeScript 编写的,但是你也可以用 JavaScript 编写“客户端”程序。

第一个 Deno 程序

让我们运行第一个 Deno 应用程序。

我感到非常惊奇的是,甚至不需要写一行代码代码——你可以从任何 URL 运行命令。

Deno下载程序,进行编译,然后运行:

img

当然,从互联网上运行任意代码不是一种\建议做法。不过我们是从 Deno 官方网站上运行它的,另外,Deno 还有一个沙箱,可以阻止程序执行你不希望做的任何事情。稍后再详细介绍。

这个程序非常简单,只需调用 console.log() 即可:

console.log('Welcome to Deno 🦕')

如果用浏览器打开 URL https://deno.land/std/examples/welcome.ts ,则会看到以下页面:

img

奇怪吧?你可能希望拿到 TypeScript 文件,但是却得到了一个网页。原因是 Deno 网站的 Web 服务器知道你正在使用浏览器,并为你提供了更加用户友好的页面。

例如,用 wget 下载相同的UR,它要求使用 text/plain 版本而不是 text/html

img

如果你想再次运行该程序,那么现在它已由 Deno 缓存,不需要再次下载:

img

你可以使用 `--reload 标志来强制重新加载原始源:

img

deno run 有许多没有在 deno --help 中列出的选项。你需要运行 deno run --help 来显示它们:

flavio@mbp~> deno run --help
deno-run
Run a program given a filename or url to the module.

By default all programs are run in sandbox without access to disk, network or
ability to spawn subprocesses.
  deno run https://deno.land/std/examples/welcome.ts

Grant all permissions:
  deno run -A https://deno.land/std/http/file_server.ts

Grant permission to read from disk and listen to network:
  deno run --allow-read --allow-net https://deno.land/std/http/file_server.ts

Grant permission to read whitelisted files from disk:
  deno run --allow-read=/etc https://deno.land/std/http/file_server.ts

USAGE:
    deno run [OPTIONS] <SCRIPT_ARG>...

OPTIONS:
    -A, --allow-all
            Allow all permissions

        --allow-env
            Allow environment access

        --allow-hrtime
            Allow high resolution time measurement

        --allow-net=<allow-net>
            Allow network access

        --allow-plugin
            Allow loading plugins

        --allow-read=<allow-read>
            Allow file system read access

        --allow-run
            Allow running subprocesses

        --allow-write=<allow-write>
            Allow file system write access

        --cached-only
            Require that remote dependencies are already cached

        --cert <FILE>
            Load certificate authority from PEM encoded file

    -c, --config <FILE>
            Load tsconfig.json configuration file

    -h, --help
            Prints help information

        --importmap <FILE>
            UNSTABLE:
            Load import map file
            Docs: https://deno.land/std/manual.md#import-maps
            Specification: https://wicg.github.io/import-maps/
            Examples: https://github.com/WICG/import-maps#the-import-map
        --inspect=<HOST:PORT>
            activate inspector on host:port (default: 127.0.0.1:9229)

        --inspect-brk=<HOST:PORT>
            activate inspector on host:port and break at start of user script

        --lock <FILE>
            Check the specified lock file

        --lock-write
            Write lock file. Use with --lock.

    -L, --log-level <log-level>
            Set log level [possible values: debug, info]

        --no-remote
            Do not resolve remote modules

    -q, --quiet
            Suppress diagnostic output
            By default, subcommands print human-readable diagnostic messages to stderr.
            If the flag is set, restrict these messages to errors.
    -r, --reload=<CACHE_BLACKLIST>
            Reload source code cache (recompile TypeScript)
            --reload
              Reload everything
            --reload=https://deno.land/std
              Reload only standard modules
            --reload=https://deno.land/std/fs/utils.ts,https://deno.land/std/fmt/colors.ts
              Reloads specific modules
        --seed <NUMBER>
            Seed Math.random()

        --unstable
            Enable unstable APIs

        --v8-flags=<v8-flags>
            Set V8 command line options. For help: --v8-flags=--help


ARGS:
    <SCRIPT_ARG>...
            script args

Deno 代码示例

除了上面运行的示例外,Deno 网站还提供了一些其他例子,你可以在这找到它们:https://deno.land/std/examples/。

在撰写本文时,我们可以找到:

  • cat.ts: 打印作为参数提供的文件列表的内容
  • catj.ts: 打印作为参数提供的文件列表的内容
  • chat/: 一个聊天程序的实现
  • colors.ts: 一个例子
  • curl.tscurl的简单实现,可打印作为参数输入的 URL 的内容
  • echo_server.ts: 一个 TCP 回显服务器
  • gist.ts:一个将文件发布到 gist.github.com 的程序
  • test.ts:测试的例子
  • welcome.ts:一个简单的 console.log 语句(我们在上面运行的第一个程序)
  • xeval.ts 允许你为收到的任何标准输入行运行任何 TypeScript 代码。 曾经是 deno xeval 命令,但此后已从官方命令中删除。

你自己的第一个 Deno 程序

让我们写一些代码。

你使用 deno run https://deno.land/std/examples/welcome.ts 运行的 Deno 程序是别人写的,所以你对 Deno 代码的样子一无所知。

我们将从 Deno 官方网站上列出的默认示例开始:

import { serve } from 'https://deno.land/std/http/server.ts'
const s = serve({ port: 8000 })
console.log('http://localhost:8000/')
for await (const req of s) {
    req.respond({ body: 'Hello World\n' })
}

该代码从 http/server 模块导入 serve 函数。看到没?我们不必事先安装它,也不会像 Node 模块那样将其存储在本地计算机上。这是 Deno 安装如此之快的原因之一。

https://deno.land/std/http/server.ts 会导入模块的最新版本。你可以使用 @VERSION 导入特定版本,如下所示:

import { serve } from 'https://deno.land/std@v0.42.0/http/server.ts'

在这个文件中,serve 函数的定义如下:

/**
 * Create a HTTP server
 *
 *     import { serve } from "https://deno.land/std/http/server.ts";
 *     const body = "Hello World\n";
 *     const s = serve({ port: 8000 });
 *     for await (const req of s) {
 *       req.respond({ body });
 *     }
 */
export function serve(addr: string | HTTPOptions): Server {
  if (typeof addr === 'string') {
    const [hostname, port] = addr.split(':')
    addr = { hostname, port: Number(port) }
  }

  const listener = listen(addr)
  return new Server(listener)
}

我们继续实例化一个调用 serve() 函数的服务器,该服务器传递带有 port 属性的对象。

然后,我们运行这个循环来响应来自服务器的每个请求。

for await (const req of s) {
  req.respond({ body: 'Hello World\n' })
}

请注意,由于 Deno 实现了 top-level await,因此无需使用 await 关键字即可将其包装到 async 函数中。

让我们在本地运行该程序。假设你用的是 VS Code,不过你可以使用任何喜欢的编辑器。

我建议从 justjavac 安装 Deno 扩展(我尝试时有另一个名称相同,但已弃用的扩展——将来可能会消失)

img

该扩展将提供一些能够给 VS Code 带来好处的实用工具,帮助你编写程序。

下面在文件夹中创建一个 app.ts 文件,并粘贴上面的代码:

img

然后用 deno run app.ts 运行:

img

Deno 首先下载我们导入的依赖项,然后再下载所需的所有依赖项。

https://deno.land/std/http/server.ts 文件本身有多个依赖项:

import { encode } from '../encoding/utf8.ts'
import { BufReader, BufWriter } from '../io/bufio.ts'
import { assert } from '../testing/asserts.ts'
import { deferred, Deferred, MuxAsyncIterator } from '../async/mod.ts'
import {
  bodyReader,
  chunkedBodyReader,
  emptyReader,
  writeResponse,
  readRequest,
} from './_io.ts'
import Listener = Deno.Listener
import Conn = Deno.Conn
import Reader = Deno.Reader

并且这些都是自动导入的。

尽管最后我们遇到了一个问题:

img

怎么回事?我们遇到了一个权限被拒绝的问题。

接下来要谈谈沙箱。

Deno 沙箱

之前我曾经提到过,Deno 的沙箱可以防止程序执行你不希望做的任何事情。

这是什么意思?

Ryan 在 Deno 简介演讲中提到过,有时你想在 Web 浏览器之外运行 JavaScript 程序,但又不想让它访问系统上任何内容,或使用网络与外界对话。

没有什么方法能够阻止 Node.js 应用获取你系统上的 SSH 密钥或任何其他的东西,并将其发送到服务器。这就是为什么我们通常只从受信任的源安装 Node 软件包的原因。但是,我们怎么知道自己使用的项目是否遭到黑客入侵,而其他人是否被黑客入侵呢?

Deno 尝试复制与浏览相同的权限模型。除非你明确允许,否则在浏览器中运行的 JavaScript 不会在你的系统上做任何令人作呕的事情。

回到 Deno,如果一个程序想要像以前那样访问网络,那么我们需要给它权限。

可以通过在运行命令时传递一个标志来实现,在本例中是 --allow-net

deno run --allow-net app.ts

img

现在该程序可以在端口 8000 上运行 HTTP 服务器了:

img

有很多允许 Deno 解锁其他功能的标志:

  • --allow-env 允许环境访问
  • --allow-hrtime 允许高精度的时间测量
  • --allow-net = 允许网络访问
  • --allow-plugin 允许加载插件
  • --allow-read = 允许文件系统读访问
  • --allow-run 允许运行子进程
  • --allow-write =允许文件系统写访问
  • --allow-all 允许所有权限(与 -A 相同)

netreadwrite 的权限可以是细粒度的。例如你可以用 --allow-read=/dev 来允许从特定目录中读取

格式化代码

我喜欢 Go 语言的一个原因是 Go 编译器附带的 gofmt 命令。所有的 Go 代码看起来都一样。每个人都在用 gofmt

JavaScript 程序员习惯于运行 Prettierdeno fmt 实际上是在后台运行的。

假设你有一个格式很乱的文件,如下所示:

img

运行 deno fmt app.ts,它会自动被正确的格式化,还会在缺少分号的地方自动添加:

img

标准库

尽管这个项目还很年轻,但 Deno 的标准库仍然很庞大。

其中包括:

  • archive: tar 存档工具
  • async async utilties
  • async:异步工具
  • bytes:用来操作字节片段的辅助
  • datetime: 日期/时间解析
  • encoding :各种格式的编码/解码功能
  • flags: 解析命令行标志
  • fmt: 格式化和打印
  • fs:文件系统 API
  • hash:加密库
  • http: HTTP服务器
  • io: I/O 库
  • log: 日志实用工具
  • mime:支持 multipart 数据
  • node: Node.js 兼容性层
  • path:路径操作
  • ws: websockets

另一个Deno示例

让我们查看另一个 Deno 应用示例:cat

const filenames = Deno.args
for (const filename of filenames) {
  const file = await Deno.open(filename)
  await Deno.copy(file, Deno.stdout)
  file.close()
}

这会将 Deno.args 的内容赋值给 filenames 变量,这一变量是包含发送到命令的所有参数的变量。

我们遍历它们,对于每一个文件名我们都用 Deno.open()打开文件,然后使用 Deno.copy() 将文件的内容打印到 Deno.stdout。最后关闭文件。

如果你这样执行:

deno run https://deno.land/std/examples/cat.ts

那么该程序会被下载并编译,但没有任何反应,因为我们没有指定任何参数。

现在试试

deno run https://deno.land/std/examples/cat.ts app.ts

假设你在同一文件夹中有上一个项目的 app.ts,会看到权限错误:

img

因为在默认情况下 Deno 是不允许访问文件系统的。使用 --allow-read=./ 可以授予对当前文件夹的访问权限:

deno run --allow-read=./ https://deno.land/std/examples/cat.ts app.ts

img

Deno 是否有 Express/Hapi/Koa ?

当然有类似的项目:

示例:使用 Oak 构建 REST API

我想举一个简单的例子,说明如何用 Oak 构建 REST API。 Oak 之所以有趣,是因为它受到了流行的 Node.js 中间件 Koa 的启发,因此,如果你以前用过,将会非常熟悉。

要构建的API非常简单。我们的服务器将会在内存中存储带有名称和年龄的狗的列表。

我们想:

  • 添加新狗
  • 列出狗
  • 获取特定狗的详细信息
  • 从列表中删除一条狗
  • 更新狗的年龄

我们将用 TypeScript 进行这些操作,当然你也可以用 JavaScript 编写 API —— 只需去掉类型就可以了。

创建一个 app.ts 文件。

首先从 Oak 导入 ApplicationRouter 对象:

import { Application, Router } from 'https://deno.land/x/oak/mod.ts'

然后得到环境变量 PORT 和 HOST:

const env = Deno.env.toObject()
const PORT = env.PORT || 4000
const HOST = env.HOST || '127.0.0.1'

默认情况下,我们的程序将运行在 localhost:4000 上。

接下来创建 Oak 应用程序并启动它:

const router = new Router()

const app = new Application()

app.use(router.routes())
app.use(router.allowedMethods())

console.log(`Listening on port ${PORT}...`)

await app.listen(`${HOST}:${PORT}`)

现在程序应该可以正常编译了。

运行

deno run --allow-env --allow-net app.ts

然后 Deno 将会下载依赖项:

img

最后侦听 4000 端口。

下次运行命令时,Deno 将会跳过安装部分,因为这些软件包已经被缓存了:

img

在文件开始,我们为狗定义一个接口,然后声明一个初始的 Dog 对象 dogs 数组:

interface Dog {
  name: string
  age: number
}

let dogs: Array<Dog> = [
  {
    name: 'Roger',
    age: 8,
  },
  {
    name: 'Syd',
    age: 7,  },
]

现在开始实现 API。

准备就绪。在创建路由器后,让我们添加一些将被调用的功能:

const router = new Router()

router
  .get('/dogs', getDogs)
  .get('/dogs/:name', getDog)
  .post('/dogs', addDog)
  .put('/dogs/:name', updateDog)
  .delete('/dogs/:name', removeDog)

看到了吗?我们定义了以下这些:

  • GET /dogs
  • GET /dogs/:name
  • POST /dogs
  • PUT /dogs/:name
  • DELETE /dogs/:name

让我们一一实现。

GET/dogs 开始,它返回所有狗的列表:

export const getDogs = ({ response }: { response: any }) => {
  response.body = dogs
}

img

接下来,是通过名称检索一只狗的方法:

export const getDog = ({
  params,
  response,
}: {
  params: {
    name: string
  }
  response: any
}) => {
  const dog = dogs.filter((dog) => dog.name === params.name)
  if (dog.length) {
    response.status = 200
    response.body = dog[0]
    return
  }

  response.status = 400
  response.body = { msg: `Cannot find dog ${params.name}` }
}

img

这是我们添加新 dog 的方法:

export const addDog = async ({
  request,
  response,
}: {
  request: any
  response: any
}) => {
  const body = await request.body()
  const dog: Dog = body.value
  dogs.push(dog)

  response.body = { msg: 'OK' }
  response.status = 200
}

img

注意,我现在使用 const body = await request.body() 来获取 body 的内容,因为 nameage 的值是作为 JSON 传递的。

这是更新狗年龄的方法:

export const updateDog = async ({
  params,
  request,
  response,
}: {
  params: {
    name: string
  }
  request: any
  response: any
}) => {
  const temp = dogs.filter((existingDog) => existingDog.name === params.name)
  const body = await request.body()
  const { age }: { age: number } = body.value

  if (temp.length) {
    temp[0].age = age
    response.status = 200
    response.body = { msg: 'OK' }
    return
  }

  response.status = 400
  response.body = { msg: `Cannot find dog ${params.name}` }
}

img

这是从列表中删除狗的方法:

export const removeDog = ({
  params,
  response,
}: {
  params: {
    name: string
  }
  response: any
}) => {
  const lengthBefore = dogs.length
  dogs = dogs.filter((dog) => dog.name !== params.name)

  if (dogs.length === lengthBefore) {
    response.status = 400
    response.body = { msg: `Cannot find dog ${params.name}` }
    return
  }

  response.body = { msg: 'OK' }
  response.status = 200
}

img

以下是完整的代码:

import { Application, Router } from 'https://deno.land/x/oak/mod.ts'

const env = Deno.env.toObject()
const PORT = env.PORT || 4000
const HOST = env.HOST || '127.0.0.1'

interface Dog {
  name: string
  age: number
}

let dogs: Array<Dog> = [
  {
    name: 'Roger',
    age: 8,
  },
  {
    name: 'Syd',
    age: 7,
  },
]

export const getDogs = ({ response }: { response: any }) => {
  response.body = dogs
}

export const getDog = ({
  params,
  response,
}: {
  params: {
    name: string
  }
  response: any
}) => {
  const dog = dogs.filter((dog) => dog.name === params.name)
  if (dog.length) {
    response.status = 200
    response.body = dog[0]
    return
  }

  response.status = 400
  response.body = { msg: `Cannot find dog ${params.name}` }
}

export const addDog = async ({
  request,
  response,
}: {
  request: any
  response: any
}) => {
  const body = await request.body()
  const { name, age }: { name: string; age: number } = body.value
  dogs.push({
    name: name,
    age: age,
  })

  response.body = { msg: 'OK' }
  response.status = 200
}

export const updateDog = async ({
  params,
  request,
  response,
}: {
  params: {
    name: string
  }
  request: any
  response: any
}) => {
  const temp = dogs.filter((existingDog) => existingDog.name === params.name)
  const body = await request.body() 
  const { age }: { age: number } = body.value

  if (temp.length) {
    temp[0].age = age
    response.status = 200
    response.body = { msg: 'OK' }
    return
  }

  response.status = 400
  response.body = { msg: `Cannot find dog ${params.name}` }
}

export const removeDog = ({
  params,
  response,
}: {
  params: {
    name: string
  }
  response: any
}) => {
  const lengthBefore = dogs.length
  dogs = dogs.filter((dog) => dog.name !== params.name)

  if (dogs.length === lengthBefore) {
    response.status = 400
    response.body = { msg: `Cannot find dog ${params.name}` }
    return
  }

  response.body = { msg: 'OK' }
  response.status = 200
}

const router = new Router()
router
  .get('/dogs', getDogs)
  .get('/dogs/:name', getDog)
  .post('/dogs', addDog)
  .put('/dogs/:name', updateDog)
  .delete('/dogs/:name', removeDog)

const app = new Application()

app.use(router.routes())
app.use(router.allowedMethods())

console.log(`Listening on port ${PORT}...`)

await app.listen(`${HOST}:${PORT}`)

更多资源

Deno 官方网站是 https://deno.land

可在 https://doc.deno.landhttps://deno.land/typedoc/index.html 上得到 API 文档。

awesome-deno https://github.com/denolib/awesome-deno

一些小花絮

  • Deno 提供了一个内置的 fetch 实现,与浏览器中的相匹配
  • Deno 与 Node.js stdlib 有一个兼容层正在进行开发中

最后的话

希望你喜欢这个 Deno 教程!