如何部署 Deno 应用程序?
使用 Deno 构建的网络应用程序有多种部署选项。不过,由于容器化作为一种服务平台与其他部署选项相比具有各种优势,近来已成为一种流行的选择。
在本文中,您将了解 Deno 及其优势和局限性。此外,您还将构建一个简单的 Deno 应用程序并将其部署到 Back4app 容器上。
Contents
什么是 Deno?
Deno是 JavaScript 和 TypeScript 的安全现代运行时,旨在解决 Node.js 中发现的局限性和设计缺陷。
与 Node.js 不同,它默认强调安全性,对文件系统和网络访问执行细粒度权限。
此外,Deno 本机支持 TypeScript,除其他功能外,无需额外的设置或转译步骤。
自 2018 年发布以来,Deno 因其对 Node.js 的改进而赢得了开发人员的关注和兴趣。
不过,虽然 Deno 有所改进,但 Node.js 仍然是一个成熟的生态系统,拥有广泛的社区支持和庞大的软件包库。
尽管如此,Deno 已吸引了越来越多的开发人员,他们欣赏其方法,并正在探索其潜力。
Deno的优势
Deno 受欢迎的原因有很多。其中一些原因如下。
安全性高于 Node.js
Deno 的一个主要优势是提高了安全性,实施了基于权限的安全模型,并在沙箱环境中运行应用程序。
它采用基于权限的安全模式,访问文件系统和网络等资源需要明确授权。
默认情况下,Deno 以受限模式运行,在沙箱环境中运行应用程序,限制潜在的风险操作,将其与底层系统隔离,并防止直接访问敏感资源。
全面的安全审计和细致的代码审查进一步加强了 Deno 强大的安全性。这些措施为您提供了一个可靠、安全的平台,用于构建应用程序,使您对其安全性充满信心,并防止潜在的漏洞。
依赖性管理
与 Node.js 等传统 JavaScript 运行时环境相比,Deno 提供了一种独特的依赖性管理方法。
Deno 不依赖中央软件包注册表,而是利用 URL 直接从网上导入模块。
这种方法不需要 npm 等单独的软件包管理器,还能减少版本冲突和管理 “node_modules “文件夹的复杂性,从而简化了流程。
您可以通过指定他们想要导入的模块的 URL 来指定依赖关系,从而使代码的共享和分发变得更加容易。在 Deno 中,这种分散式的依赖关系管理方法促进了简单性,减少了摩擦,并有助于确保更简化的开发体验。
开箱即用的 TypeScript 支持
Deno 为 TypeScript 提供本机无缝支持,如果你喜欢或需要在项目中使用 TypeScript,它将是你的最佳选择。
TypeScript 是 JavaScript 的类型超集,为 JavaScript 开发带来了静态类型和其他高级语言特性。有了Deno,使用TypeScript不需要额外的配置或构建步骤。
Deno 捆绑了 TypeScript 编译器,允许你直接编写和运行 TypeScript 代码。
这种本机支持消除了单独设置 TypeScript 工具链的复杂性,简化了开发流程。
它使您在使用 Deno 构建应用程序时,能够利用 TypeScript 的类型检查、改进的工具和增强的开发人员体验。
Deno的局限性
然而,Deno 有一些局限性,影响了它的应用。其中一些限制如下。
不成熟的生态系统
Deno 的局限性之一在于其生态系统的成熟度。与存在时间更长的 Node.js 相比,Deno 的生态系统仍然相对较新,而且还在不断发展。
这意味着专为 Deno 设计的第三方库、框架和工具可能较少。您可能需要从头开始构建某些功能,或者调整现有的 Node.js 软件包以便在 Deno 中使用。
与完善的 Node.js 生态系统相比,较小的社区规模也意味着可用的资源、教程和社区支持可能较少。
不过,随着 Deno 的普及和应用,其生态系统预计将不断发展和成熟,未来将提供更广泛的库和工具。
陡峭的学习曲线
Deno 的另一个局限是,从 Node.js 等其他 JavaScript 运行时环境过渡到 Deno 时,学习曲线较长。
Deno 引入了新的概念、应用程序接口和模式,你可能需要熟悉这些概念、应用程序接口和模式。这包括了解 Deno 的模块系统、基于权限的安全模型,以及某些功能的实现方式与其他运行时的不同之处。
已经精通 Node.js 的开发人员可能需要投入时间和精力来学习和适应 Deno 的特定功能和惯例。
不过,学习曲线可以通过参考 Deno 官方文档、参与 Deno 社区和探索可用的学习资源来控制。
与 Node.js 库的兼容性
与 Node.js 库的兼容性是 Deno 的另一个限制。由于模块系统和运行时环境的差异,并非所有 Node.js 库和模块都能不加修改地直接在 Deno 中使用。
Deno 使用 ES 模块(ECMAScript 模块)作为其模块系统,而 Node.js 传统上使用 CommonJS 模块。在 Deno 中导入和使用特定于 Node.js 的模块时,模块格式的这种差异可能会导致不兼容。
开发人员可能需要进行调整,或寻找专门设计用于与 Deno 模块系统配合使用的替代库。
虽然 Deno 提供了运行某些 Node.js 模块的兼容性层,但它可能无法涵盖所有情况,可能需要手动修改或调整。
Deno 部署选项
Deno 应用程序有多种部署选项,其中包括以下几种。
基础设施即服务(IaaS)
基础设施即服务(IaaS)是一种提供虚拟化计算资源的云计算模式。通过 IaaS,您可以按需付费的方式从云提供商处租用虚拟机、存储和网络。这样,您就可以建立和管理自己的虚拟化基础设施,而无需投资物理硬件。
IaaS 选项使您能够在虚拟机上运行 Deno 应用程序。流行的 IaaS 平台,如 AWS、Google Cloud 和 Microsoft Azure,提供灵活且可扩展的解决方案,使您能够根据应用程序的特定需求配置基础设施。
不过,虽然 IaaS 能让您获得更强的控制力和资源隔离能力,但它也需要更多的手动设置和管理,涉及服务器配置、安全更新和监控等任务。
因此,当您需要对基础设施进行广泛控制,并拥有有效处理其复杂性的专业知识时,IaaS 是一个可行的选择。
容器即服务(CaaS)
容器即服务(CaaS)是一种云计算模式,可简化容器化应用程序的部署和管理。
有了 CaaS,您就可以专注于构建和部署应用程序,而不必担心底层基础设施。
Deno 应用程序可以部署在容器中,确保一致性和隔离性。Back4app 容器是用于部署 Deno 的一种流行的 CaaS 选项。
CaaS 平台具有可扩展性和资源隔离性,每个应用程序都在自己的容器中运行,从而提高了安全性和稳定性。
容器的一致性确保了 Deno 应用程序可以在任何支持容器的平台上轻松部署。
虽然 CaaS 解决方案有一定的学习曲线,但对于需要跨多个节点或集群进行动态扩展和部署的应用程序来说,它们具有显著的优势。
Deno 安装过程
在使用 Deno 之前,您必须下载并安装它。Deno 的安装因操作系统而异。
在 macOS 和 Linux 上,运行以下命令即可安装 Deno:
curl -fsSL <https://deno.land/x/install/install.sh> | sh
在 Windows 系统中,您可以使用 Powershell 运行以下命令来安装 Deno:
irm <https://deno.land/install.ps1> | iex
要确认安装是否成功,可以运行下面的命令,它会在终端上打印出版本号。
deno --version
如果看不到版本号,请尝试重新安装 Deno。
设置 Deno 项目
要使用 Deno 创建一个简单的应用程序接口,您需要一个路由器、一个服务器和一个数据库。
在执行以下步骤之前,请在项目根目录下创建一个src
文件夹。该文件夹将包含项目的所有源文件。
步骤 1:创建依赖关系文件
与 Node.js 不同,Deno 不使用 NPM 或 Yarn 等软件包管理器。相反,软件包是直接从其 URL 导入的。
要模仿package.json
文件的功能,请在项目根目录下创建一个deps.ts
,并将下面的代码块添加到其中。
export { Application, Router } from "https://deno.land/x/[email protected]/mod.ts";
export type { RouterContext} from "https://deno.land/x/[email protected]/mod.ts";
export { config as dotenvConfig } from "https://deno.land/x/[email protected]/mod.ts";
export { Client } from "https://deno.land/x/[email protected]/mod.ts";
上面的代码块从 Oak 导入(安装)和导出Application
、Router
和RouterContex
,从 dotenv 导入config
,从 deno-postgres 导入Client
。
步骤 2:创建服务器
在本步骤中,您将使用 Oak 创建一个简单的 HTTP 服务器。Oak 是 Deno HTTP 服务器的中间件,基于 Koa.js。Koa.js 是 Node.js 的一个框架,类似于 Express,但更轻量级。
要使用 Oak 创建 HTTP 服务器,请在src
中创建server.ts
文件,并添加以下代码块。
import { Application } from "../deps.ts";
import config from "../config/default.ts";
import router from "./router.ts";
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: config.port });
上面的代码块创建了一个带有 Oak 的 HTTP 服务器,并注册了一个路由器来处理所有进入的流量。
app.use(router.routes())
行将路由器的路由注册为 Oak 应用程序的中间件。所有传入的请求都将与注册的路由进行匹配,如果发现匹配,就会执行相应的处理程序。
如果未找到匹配的方法
,app.use(router.allowedMethods())
行会通过发送 404 未找到或 405 不允许等适当的响应来处理它们。
步骤 3:管理环境变数
以纯文本存储 API 密钥、数据库凭据等敏感数据会带来安全风险。任何掌握了您的密钥或凭证的人都可能不受限制地访问您的应用程序。这可能导致数据丢失和数据被盗,以及其他可能的漏洞。
在环境变量中存储敏感数据被认为是避免类似情况的良好做法。
在项目根文件夹中创建.env
文件,并在文件中存储数据库凭据和其他敏感信息。
就像这样
#.env
DB_URI = <YOUR_POSTGRES_DB_URI>
PORT = 8000
将 替换为您的数据库凭据。
然后,在项目根文件夹中创建一个config
文件夹,在 config 文件夹中创建一个default.ts
文件,并将下面的代码块添加到该文件中。
//default.ts
import { dotenvConfig } from "../deps.ts";
dotenvConfig({
export: true,
path: "../.env",
});
const config = {
db: {
dbUri: Deno.env.get("DB_URI"),
},
port: 3000
};
export default config;
上面的代码块可以安全地检索存储在.env
文件中的值,并将其公开给应用程序的其他部分。
步骤 3:连接数据库
在这一步中,您将把应用程序连接到 Postgres 数据库。您将需要数据库来为您的应用程序存储和检索数据。
在src
文件夹中创建一个db.ts
文件,并添加以下代码块。
//db.ts
import { Client } from "../deps.ts";
import config from "../config/default.ts";
let postgresConfiguration = config.db.dbUri;
const client = new Client(postgresConfiguration);
await client.connect();
export default client;
上面的代码块试图使用您在.env
文件中提供的 URI 将您的应用程序连接到 Postgres 数据库。
步骤 4:创建数据库存储库
在src
文件夹中创建blogRepository
文件,并在文件中添加以下代码。
//blogRepository.ts
import client from "./db.ts";
class BlogRepository {
async createBlogTable() {
const blog = await client.queryArray(
`CREATE TABLE IF NOT EXISTS blogs (id SERIAL PRIMARY KEY, title VARCHAR(255), body VARCHAR(255), author VARCHAR(255))`
);
return blog;
}
async getAllBlogs() {
const allBlogs = await client.queryArray("SELECT * FROM blogs");
return allBlogs;
}
async getBlogById(id: string) {
const blog = await client.queryArray(
`SELECT * FROM blogs WHERE id = ${id}`
);
return blog;
}
async createBlog(title: string, body: string, author: string) {
const blog = await client.queryArray(
`INSERT INTO blogs (title, body, author) VALUES ('${title}', '${body}', '${author}')`
);
return blog;
}
async updateBlog(id: string, title: string, body: string, author: string) {
const blog = await client.queryArray(
`UPDATE blogs SET title = '${title}', body = '${body}', author = '${author}' WHERE id = ${id}`
);
return blog;
}
async deleteBlog(id: string) {
const blog = await client.queryArray(`DELETE FROM blogs WHERE id = ${id}`);
return blog;
}
}
export default new BlogRepository();
上面的代码块将通过抽象原始 SQL 查询和暴露简单方法来处理所有数据库操作,您可以使用这些方法与 Postgres 数据库交互。
步骤 5:创建路由处理程序
在此步骤中,您将创建路由处理程序,为应用程序处理简单的 CRUD 功能。支持的路由如下:
- GET /api/blogs:返回数据库中的所有博客
- GET /api/blog/:id:返回与 URL 参数中提供的 id 匹配的单个博客。
- POST /api/blog/new:在数据库中创建一个新博客。
- PUT /api/blog/:id:用 URL 参数中提供的匹配 id 更新博客。
- DELETE /api/blog/:id:删除与 URL 参数中提供的 id 匹配的博客。
在 src 文件夹中创建一个 router.ts 文件,并添加以下导入。
import { Router, RouterContext } from "../deps.ts";
import blogRepository from "./blogRepository.ts";
接下来,创建一个路由器实例,将下面的代码块添加到路由器实例中:
const router = new Router();
要注册路由器处理程序,必须将它们链入路由器实例。
例如(GET /api/blogs):
router
.get("/api/blogs", async (ctx: RouterContext<"/api/blogs">) => {
const data = await blogRepository.getAllBlogs();
console.log(data);
//format data
const allBlogs = data.rows.map((blog) => {
return {
id: blog[0],
title: blog[1],
body: blog[2],
author: blog[3]
};
});
ctx.response.body = allBlogs;
})
上面的代码块通过将处理程序逻辑链入路由器实例,为 GET /api/blogs 创建了一个路由处理程序。
将它们链接到之前的链接方法,以注册其余路线。就像这样
GET /api/blog/:id:
.get("/api/blog/:id", async (ctx: RouterContext<"/api/blog/:id">) => {
try {
const data = await blogRepository.getBlogById(ctx.params.id);
console.log(data);
//format data
const blog = data.rows.map((blog) => {
return {
id: blog[0],
title: blog[1],
body: blog[2],
author: blog[3]
};
});
ctx.response.body = blog;
} catch (error) {
ctx.response.status = 500;
ctx.response.body = {
msg: "Error getting blog",
error,
};
}
})
POST /api/blog/new
.post("/api/blog/new", async (ctx: RouterContext<"/api/blog/new">) => {
const resBody = ctx.request.body();
const blog = await resBody.value;
if (!blog) {
ctx.response.status = 400;
ctx.response.body = { msg: "Invalid data. Please provide a valid blog." };
return;
}
const { title, body, author } = blog;
if (!(title && body && author)) {
ctx.response.status = 400;
ctx.response.body = {
msg: "Title or description missing. Please provide a valid blog.",
};
return;
}
try {
await blogRepository.createBlog(title, body, author);
ctx.response.status = 201;
ctx.response.body = {
msg: "blog added successfully",
};
} catch (error) {
ctx.response.status = 500;
ctx.response.body = {
msg: "Error adding blog",
error,
};
}
})
PUT /api/blog/:id:
.put("/api/blog/:id", async (ctx: RouterContext<"/api/blog/:id">) => {
try {
const resBody = ctx.request.body();
const blog = await resBody.value;
if (!blog) {
ctx.response.status = 400;
ctx.response.body = {
msg: "Invalid data. Please provide a valid blog.",
};
return;
}
const { title, body, author } = blog;
if (!(title && body && author)) {
ctx.response.status = 400;
ctx.response.body = {
msg: "Title or description missing. Please provide a valid blog.",
};
return;
}
await blogRepository.updateBlog(ctx.params.id, title, body, author);
ctx.response.status = 200;
ctx.response.body = {
msg: "blog updated successfully",
};
} catch (error) {
console.log(error);
ctx.response.status = 500;
ctx.response.body = {
msg: "Error updating blog",
error: error.message,
};
}
})
DELETE /api/blog/:id:
.delete("/api/blog/:id", async (ctx: RouterContext<"/api/blog/:id">) => {
await blogRepository.deleteBlog(ctx.params.id);
ctx.response.status = 200;
ctx.response.body = {
msg: "blog deleted successfully",
};
});
然后,导出路由器实例。就像这样
export default router;
最后,修改server.ts
文件,以便在应用程序首次启动时创建博客数据库。
就像这样
import { Application } from "../deps.ts";
import config from "../config/default.ts";
import blogRepository from "./blogRepository.ts";
import router from "./router.ts";
const app = new Application();
(async () => {
await blogRepository.createBlogTable();
})();
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: config.port });
修改后的代码在server.ts
文件中添加了一个 IIFE,用于在数据库中创建一个新的博客表。
在 Back4app 容器上部署 Deno 应用程序
要在 Back4app 容器上部署 Deno 应用程序,需要遵循以下步骤:
步骤 1:创建 Dockerfile
Dockerfile 提供了构建 Docker 映像的具体说明。这些说明指导着构建镜像的过程。
运行下面的命令创建 Dockerfile:
touch Dockerfile
上述命令会在项目根目录下创建一个 Dockerfile。
接下来,将下面的代码块添加到 Dockerfile 中:
FROM denoland/deno:latest
EXPOSE 8000
WORKDIR /app
COPY deps.ts .
RUN deno cache deps.ts
COPY . .
RUN deno cache src/server.ts
CMD ["run", "--allow-net", "--allow-env", "--allow-read", "src/server.ts"]
上面的 Dockerfile 为运行 Deno 应用程序设置了一个容器化环境。它缓存了应用程序的依赖关系和入口点,然后在启动容器时以指定权限运行 Deno 应用程序。
虽然实际的端口映射必须在运行容器时完成,但应用程序应监听端口 8000。
最后,将代码推送到 GitHub。
第 2 步:创建新的 Back4app 应用程序
要创建 Back4app 应用程序,请访问Back4app 官方网站。访问后,找到 Back4app 登陆页面右上角的注册按钮。点击注册按钮后,您将被引导至注册表。在该表格中填写所需的详细信息,如您的电子邮件地址、用户名和密码。确保提供准确的信息。填写完成后,提交表格。
如果您已有账户,请单击 “登录”。
成功设置 Back4app 帐户后,请登录访问您的帐户控制面板。在那里找到 “NEW APP “按钮并点击。
此操作将引导您进入一个页面,在这里您将看到创建新应用程序的不同选项。由于您打算使用容器化进行部署,因此请选择“容器即服务“选项。
接下来,将您的 GitHub 账户连接到 Back4app 账户。您可以让 Back4app 访问您账户中的所有仓库或特定仓库。
选择要部署的应用程序,在本例中就是本教程中构建的应用程序,然后单击 “选择“。
单击 “选择 “按钮将跳转到一个页面,在此您需要填写有关应用程序的一些信息,如名称、分支、根目录、自动部署选项、端口、健康状况和环境变量。
确保提供应用程序正常运行所需的所有环境变量。完成所需信息的填写后,点击 “创建应用程序 “按钮。
这将启动部署过程,一段时间后,您的部署应该就绪了。如果部署过程耗时较长,您可以查看日志,了解部署是否出错,或查看Back4app 故障排除指南。
结论
在本文中,您将了解 Deno 及其优势、局限性、常用部署选项,以及如何使用 Deno 构建应用程序和使用 Back4app 容器部署应用程序。
尽管有其局限性,但由于其安全模型,Deno 是构建非常安全和敏感的应用程序的理想选择。此外,它对 TypeScript 的原生支持消除了在项目中设置 TypeScript 的麻烦。
按照本文概述的步骤,您可以在 Back4app 容器上轻松创建和部署 Deno 应用程序。
常见问题
什么是 Deno?
Deno 是一个安全且现代的 JavaScript/TypeScript 运行时,允许开发人员构建服务器端和客户端应用程序。
如何部署 Deno 应用程序?
1. 创建 Deno 应用
2. 创建 Dockerfile
3. 将 Deno 应用推送到 GitHub,并将 GitHub 帐户连接到 Back4app 帐户。
4. 在 Back4app 上创建 CaaS 应用
5. 从存储库列表中选择 Deno 应用
6. 部署 Deno 应用