今天我们来使用 Deno 创建一个 HTTP 服务,在这之前呢,我们来做一些准备工作。
Deno 介绍
对于对 Deno 不了解的同学,我们再来做一下 Deno 的介绍:
做前端的小伙伴都了解 Node.js,Deno 是和 Node.js 一样的,一个 JS/TS 运行时。 那么 Deno 的特点是什么呢:
-
Deno 基于最新的 JavaScript 语言;
-
Deno 具有覆盖面广泛的标准库;
-
Deno 以 TypeScript 为核心,配以更多独特的方式从而带来了巨大的优势,其中包括一流的 TypeScript 支持(Deno 自动编译 TypeScript 而无需你单独编译);
-
Deno 大力拥抱 ES 模块标准;
-
Deno 没有包管理器;
-
Deno 具有一流的 await 语法支持;
-
Deno 内置测试工具;
-
Deno 旨在尽可能地与浏览器兼容,例如通过提供内置对象 fetch 和全局 window 对象。
Deno 初始的目标可能是 Node.js 的替代品,旨在解决 Node.js 的一些痛点和历史遗留问题。那么它和 Node.js 有哪些异同呢?
相同点:
-
两者都是基于 V8 引擎开发的;
-
两者都非常适合在服务器端上编写 JavaScript 应用。
不同点:
-
Node.js 用 C++ 和 JavaScript 语言编写。Deno 用 Rust 和 TypeScript 语言编写。
-
Node.js 有一个官方的软件包管理器,称为 NPM。Deno 不会有,而会允许你从 URL 导入任何 ES 模块。
-
Node.js 使用 CommonJS 模块语法导入软件包。Deno 使用 ES 标准模块导入。
-
Deno 在其所有 API 和标准库中都使用现代 ECMAScript 功能,而 Node.js 使用基于回调的标准库,并且没有计划对其进行升级。
-
Deno 通过权限控制提供了一个安全的沙箱环境,程序只能访问由用户设置为可执行标志的文件。Node.js 程序可以直接访问用户足以访问的任何内容。
-
Deno 长期以来一直在探索将程序编译成单个可执行文件的可能性,从而使得该可执行文件可以在没有外部依赖项(例如 Go)的情况下运行,但这并不是一件容易的事,如果做得到,将会成为更有话语权的游戏规则改变者。
-
Deno 没有 npm 一样的包管理器。
安装
Deno 的安装其实很简单。
windows 安装
windows 环境,官方推荐使用 Chocolatey 安装。
首先安装 Chocolatey
那么首先,我们需要安装 Chocolatey。
用管理员模式运行 cmd,我们可在 C:\Windows\System32 下找到 cmd.exe 右键管理员运行。
输入以下命令:
@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
安装完毕后,使用 choco -v
查看是否安装成功。
安装 deno
choco install deno
使用 deno -V
查看是否安装成功。
Mac 安装
Mac 安装最简单的方法是使用 Homebrew:
sh brew install deno
安装完毕后,使用 deno -V
查看是否安装成功。
安装 Vscode 扩展
启动一个 Deno 服务
首先新建一个 index.ts 文件,写入以下代码
使用deno run index.ts
命令运行它。
先下载了一些依赖项,最后在编译的步骤上报错了,提示我们没有网络权限,这其实是 Deno 的 安全沙箱 Sandbox 的问题。Deno 有一个安全沙箱,可以防止程序做一些你不允许的事情。
那么我们在上面的运行命令上加上 --allow-net
deno run --allow-net app.ts
运行成功了,该应用程序现在监听在 8000 端口上运行着 HTTP 服务器:
构建 REST-API
我们启动了一个 Deno 服务,那么如果我们想编写一套符合 REST 标准的 API,怎么办呢。我们知道,Node.js 拥有 Express、Koa、egg 等优秀的开源框架。Deno 同样有人开发了一个 Oak 框架,从名字上我们就能看出来,它是一个类似于 Koa 的中间件框架。
那么我们接下来构建一个这样的 API:
- 添加一本书
- 获得书的列表
- 获得某本书的详情
- 删除一本书
- 更新一本书
创建一个 app.ts 文件。
从 Oak 导入 Application 和 Router 对象:
import { Application, Router } from "https://deno.land/x/oak/mod.ts";
然后我们得到环境变量 PORT 和 HOST:
const env = Deno.env.toObject();
const PORT = env.PORT || 8000;
const HOST = env.HOST || "127.0.0.1";
编写应用代码
使用命令来运行它
deno run --allow-env --allow-net app.ts
接下来我们来编写我们的 API 吧。
在文件的顶部,让我们定义一个 BOOK 的接口,然后我们声明一个初始的 books 数组 Book 对象。
interface Book {
name: string;
author: string;
introduction: string;
}
let books: Array<Book> = [
{
name: 'bookOne',
author: '张三',
introduction: '这是这本书的介绍',
},
{
name: 'bookTwo',
author: '李四',
introduction: '这是这本书的介绍',
},
]
安装 REST API 的规范定义好我们的结构
-
GET /books
-
GET /books/:name
-
POST /books
-
PUT /books/:name
-
DELETE /books/:name
那么代码这么写:
router
.get('/books', getBooks)
.get('/books/:name', getBook)
.post('/books', addBook)
.put('/books/:name', updateBook)
.delete('/books/:name', deleteBook)
接下来写每个接口的方法:
GET /books
获取所有书籍列表
// 获得所有书的列表
export const getBooks = ({ response }: { response: any }) => {
response.body = books
}
GET /books/:name
通过名字来获取某一本书的详情
// 获取某一本书
export const getBook = ({
params,
response,
}: {
params: {
name: string,
},
response: any,
}) => {
const book = books.filter((book) => book.name === params.name)
if (book.length) {
response.status = 200
response.body = book[0]
return
}
response.status = 400
response.body = { msg: `Cannot find book ${params.name}` }
}
POST /books
添加一本书
// 添加一本书
export const addBook = async ({ request, response }: { request: any, response: any }) => {
const body = await request.body()
const book: Book = body.value
books.push(book)
response.body = { msg: 'OK' }
response.status = 200
}
PUT /books/:name
更新一本书
// 更新一本书
export const updateBook = async ({
params,
request,
response,
}: {
params: {
name: string,
},
request: any,
response: any,
}) => {
const temp = books.filter((existingBook) => existingBook.name === params.name)
const body = await request.body()
const { introduction }: { introduction: string } = body.value
if (temp.length) {
temp[0].introduction = introduction
response.status = 200
response.body = { msg: 'OK' }
return
}
response.status = 400
response.body = { msg: `Cannot find book ${params.name}` }
}
DELETE /books/:name
删除一本书
// 删除一本书
export const deleteBook = ({
params,
response,
}: {
params: {
name: string,
},
response: any,
}) => {
const lengthBefore = books.length
books = books.filter((book) => book.name !== params.name)
if (books.length === lengthBefore) {
response.status = 400
response.body = { msg: `Cannot find book ${params.name}` }
return
}
response.body = { msg: 'OK' }
response.status = 200
}
好,现在所有的接口都写完了,我们可以用 postman 来进行测试。
- 获取所有书的列表
-
获取某一本书的详情
-
添加一本书
再次查询,发现刚才添加的书已经在列表中了。
- 修改一本书
再次查询,发现 bookOne 的简介更新了。
- 删除一本书
再次查询,发现 bookOne 已经从列表中删除了
以上,我们已经完成了我们的 REST API 了。可以看到 Deno 写代码与 Node.js 并没有太大的差异,不同的在于 Deno 的依赖管理方式,下一篇文章我们将讲一讲 Deno 项目如何管理引入依赖,以及它的优缺点。
本文所有代码可见:Github