将 Node Express API 迁移至无服务器
如果您拥有 Express API,那么您并不孤单。但是,您是否考虑过将这种基于服务器的 API 模型转换为无服务器模型?请继续阅读,在本文结束时,您将了解如何操作,并获得一个可供您亲自尝试的可行示例。
我很喜欢用 Node 和 Express 创建 API!然而,它们需要一台服务器,而且需要在云端支付服务器费用。迁移到无服务器架构可以降低成本和服务器维护成本,方便扩展和缩减规模,并减少构建健壮 Express 应用所需的中间件的占用空间。这完美吗?当然不是!但如果这些因素影响到你,这倒是一个不错的选择。本文将教你如何将 Node Express API 迁移到无服务器函数。
本文是#ServerlessSeptember的一部分。在这个关于无服务器的全系列内容中,您可以找到其他有用的文章、详细的教程和视频。九月份每天都会发布新文章。
详细了解 Microsoft Azure 如何实现无服务器功能,请访问https://docs.microsoft.com/azure/azure-functions。
您将学到什么
我们将首先在示例项目中探索并运行 Node 和 Express API。然后,我们将逐步介绍如何创建 Azure Functions 应用,并重构 Express 路由和对 Azure Functions 应用的数据调用。最后,我们将一起探索结果。通过此过程,你将学习:
- 创建 Azure 函数应用
- 将现有的 Express API 重构为 Azure Functions
- 了解这些方法之间的差异
虽然本文介绍了将 API 从 Express 转移到 Azure Functions 的步骤,但您也可以按照 GitHub 上已完成的示例项目进行操作。
我们将一起浏览代码和步骤,最后我会分享您开始并亲自尝试所需的一切的链接。
规划向无服务器转型
在将应用程序转移到无服务器之前,让我们思考一下为什么要这样做以及执行转移可能需要付出哪些努力。
首先,Express 应用需要一台服务器,您必须配置和维护它。如果能减轻一些这方面的工作量和成本就好了。
Express 应用通常包含大量中间件和逻辑来启动服务器。本示例项目包含的中间件数量很少,但对于需要更多关注点(例如安全性)和功能(例如日志记录)的生产应用来说,您肯定需要更多中间件。虽然 Azure Functions 并不能完全消除这些中间件,但启动 Azure Functions 所需的逻辑和代码更少。通常只需要很少的代码和一些配置。这在具体示例中意味着什么?好吧,对于本示例应用来说,server.ts文件实际上已经不存在了。
那么为什么要进行这种转变呢?总的来说,无服务器架构需要考虑的事情更少。
关于示例项目
您将在本文中了解GitHub 上的示例项目有哪些内容?好问题!
该项目代表一个用 TypeScript 编写的简单 Node Express API 应用程序,现已转移到 Azure Functions。
但是如果你不使用 TypeScript 怎么办?没关系。如果你的 Express 应用使用的是 JavaScript,可以随意将其迁移到使用 JavaScript 的 Azure Functions。
客户端应用采用 Angular 框架,但也可以轻松使用 Vue 或 React。整个应用都使用了“英雄与恶棍”主题。
虽然我们将使用 Angular 应用程序,但 Azure Functions 的一大优点是您可以在计算机上本地运行它、对其进行调试,并使用浏览器、Postman、Insomnia 等工具调用 HTTP 函数(如下所示)。
入门
让我们首先获取代码并设置开发环境。请按照以下步骤准备代码。
- 克隆此存储库
- 安装 npm 包
- 构建 Node Express 和 Angular 代码
git clone https://github.com/johnpapa/express-to-functions.git
cd express-to-functions
npm install
npm run node-ng:build
- 在项目根目录中复制env.example文件并将其命名为.env 。该文件应包含以下代码。
.env
NODE_ENV=development
PORT=7070
WWW=./
环境变量:应用程序的根目录中可能存在非常重要的环境变量,位于.env文件中。该文件不会提交到 GitHub,因为它可能包含敏感信息。
现在我们的代码已经可以使用了。但在使用之前,我们先回顾一下,看看我们有什么。
Node 和 Express API
现在让我们探索GitHub 上的示例项目。
这是一个传统的 Node 和 Express 应用程序,服务于以下八个端点。
方法 | 路线终点 |
---|---|
得到 | 英雄 |
邮政 | 英雄 |
放 | 英雄:ID |
删除 | 英雄/:id |
得到 | 恶棍 |
邮政 | 恶棍 |
放 | 恶棍:id |
删除 | 恶棍/:id |
Node Express 应用程序的结构很简单,包含在服务器文件夹中。
server
| - routes
| | - hero.routes.ts 👈 The hero routes
| | - index.ts
| | - villain.routes.ts
| - services
| | - data.ts 👈 The hero data (could be database API calls)
| | - hero.service.ts 👈 The logic to get the hero data
| | - index.ts
| | - villain.service.ts
| - index.ts
| - server.ts 👈 The Express server
| - tsconfig.json
入口点是server/index.ts文件,该文件运行server.ts代码来启动 Express 服务器。然后,从/routes文件夹加载路由(例如 /heroes)。这些路由会在/services文件夹中执行相应的代码。应用程序在data.ts文件中定义数据存储配置。
例如,当客户端应用程序对/heroes路由进行 HTTP GET 时,该路由将执行/services/hero.service.ts文件中的逻辑来获取英雄。
您可以自行探索服务器文件夹中 Express 逻辑的代码。
这是正在运行的应用程序的屏幕截图。
运行并调试 Express 应用
当我想熟悉某个应用时,我发现用调试器运行并单步调试很有帮助。让我们一起来试试吧。
让我们首先在 Visual Studio Code 中打开该应用程序。
- 打开proxy.conf.json并将端口更改为7070(我们的 Express 应用程序)
- 打开 VS Code 命令面板F1
- 输入View: Show Debug并按ENTER
- 选择Debug Express 和 Angular
- 按F5
- 注意浏览器打开http://localhost:7070
您现在可以在 Express 和 Angular 代码中设置断点。
这里调试器在 Angular 应用程序的断点处停止。
这里调试器在 Express 应用程序的断点处停止。
.vscode/launch.json和.vscode/tasks.json文件是本项目调试体验不可或缺的一部分。我鼓励您探索这些文件,并根据自己的需求复制/重构其内容。
做出改变
现在我们已经运行了应用程序,并探索了 Express 的入门步骤,接下来让我们规划一下从 Express 到无服务器的迁移。我喜欢通过将问题分解成更小的问题来解决问题。在本例中,我们首先将 Node Express 应用程序分解为三个主要部分:
- Express 服务器(主要在server.ts中)
- 路线(routes/ * )
- 数据访问逻辑(services/ .service.ts*)
我们将逐一进行迁移。首先从 Express 服务器迁移到 Azure Functions。
快递👉 Azure Functions
Express 服务器在服务器上运行 API。您可以创建一个 Azure Functions 项目来运行 API。我建议使用适用于 Azure Functions 的 VS Code 扩展。安装后,请按照以下步骤在您的计算机上创建 Azure Functions。
- 按F1打开命令面板
- 键入并选择Azure Functions: 创建新项目
- 选择“浏览”找到要创建函数的文件夹
- 在您的项目中创建一个名为functions的新文件夹
- 选择TypeScript
- 当提示创建函数时,选择“暂时跳过”
恭喜,您刚刚创建了一个 Azure Function 应用!
Azure Functions 应用程序为我们的路线提供服务。
在functions文件夹中创建函数应用有助于将其与同一项目中的 Angular 和 Express 应用区分开来。当然,您不必将它们全部放在同一个项目中,但在本示例中,将它们放在一个位置查看会很有帮助。
转移路线 - 创建您的第一个函数
您可能还记得,Express 应用中有八个端点。请按照以下步骤为第一个端点创建一个函数。我们稍后会返回并创建其他七个端点。
- 按F1打开命令面板
- 键入并选择Azure Functions: 创建函数
- 选择HTTP 触发器作为函数类型
- 输入heroes-get作为函数名称
- 选择匿名作为身份验证级别
注意,现在有一个文件夹functions/heroes-get,其中包含一些文件。function.json包含该函数的配置。打开function.json文件,你会发现这些方法同时允许 GET 和 POST。请将其更改为仅允许 GET。
默认情况下,执行此函数的路由是heroes-get。Express 应用中的路由仅仅是heroes。我们希望它们保持一致,因此在function.json的bindings部分route: "heroes"
中添加一个条目。现在,当对/heroes进行HTTP GET 请求时,该函数就会被执行。
您的function.json应该类似于以下代码。
{
"disabled": false,
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"],
"route": "heroes"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../dist/heroes-get/index.js"
}
functions/heroes-get文件夹中另一个重要的文件是index.ts。此文件包含路由调用时运行的逻辑。我们已经从 Express 应用中获取了所有这些逻辑。接下来我们将获取它们。
数据——将服务转移到无服务器
与数据存储交互的所有逻辑都包含在Express 应用的server/services文件夹中。我们可以提取这些代码,并将其转移到 Azure Functions 应用,并进行一些小的调整。这看起来似乎行不通,但让我们来思考一下 Express 应用和 Azure Functions 应用之间的区别。以下是服务方面的一些主要区别。
- Express 应用程序使用 npm 包express,而 Azure Functions 应用程序使用 npm 包@azure/functions
- Express 具有
req
表示res
请求和响应的参数。Azure Functions 将它们放在对象context
变量中。
这就是我们需要知道的全部内容。有了这些信息,我们就可以把 Express 应用中的服务代码复制到 Azure Functions 应用,并且只需进行少量修改。现在就开始吧。
将代码从 Express 转移到函数
如果没有必要,为什么要从头开始编写所有内容并放弃你的辛勤工作呢?好吧,我们可以从 Express 应用程序中获取服务代码并将其复制到 Azure Functions 应用程序中。
- 复制服务器/服务文件夹
- 粘贴到函数文件夹
现在,我们进行了一些小规模的重构,使代码能够兼容 Azure Functions 而非 Express。这里唯一改变的是路由 API 以及请求和响应的传递方式。让我们针对这个 API 差异进行重构。
- 打开functions/services/hero.service.ts文件
- 替换
import { Request, Response } from 'express';
为import { Context } from '@azure/functions';
(req: Request, res: Response)
用替换 的每个实例({ req, res }: Context)
。
重构完成后,你的代码将如下所示。注意修改的地方都已注释掉。
// 👇 This was import { Request, Response } from 'express';
import { Context } from '@azure/functions';
import * as data from './data';
// 👇 This was async function getHeroes(req: Request, res: Response) {
async function getHeroes({ req, res }: Context) {
try {
const heroes = data.getHeroes();
res.status(200).json(heroes);
} catch (error) {
res.status(500).send(error);
}
}
// 👇 This was async function postHero(req: Request, res: Response) {
async function postHero({ req, res }: Context) {
const hero = {
id: undefined,
name: req.body.name,
description: req.body.description
};
try {
const newHero = data.addHero(hero);
res.status(201).json(newHero);
} catch (error) {
res.status(500).send(error);
}
}
// 👇 This was async function putHero(req: Request, res: Response) {
async function putHero({ req, res }: Context) {
const hero = {
id: req.params.id,
name: req.body.name,
description: req.body.description
};
try {
const updatedHero = data.updateHero(hero);
res.status(200).json(updatedHero);
} catch (error) {
res.status(500).send(error);
}
}
// 👇 This was async function deleteHero(req: Request, res: Response) {
async function deleteHero({ req, res }: Context) {
const { id } = req.params;
try {
data.deleteHero(id);
res.status(200).json({});
} catch (error) {
res.status(500).send(error);
}
}
export default { getHeroes, postHero, putHero, deleteHero };
有四个函数以 request 和 respond 为参数。getHeroes
、postHero
、putHero
和分别对应一个deleteHero
。
Express 应用中每个函数的参数都包含req
和res
。Azure Functions 应用仍然可以访问请求和响应对象,但它们包含在一个context
对象中。我们使用解构来访问它们。
该
Context
对象还包含其他 API,例如log
(例如context.log('hello')
:)。这可以用来代替console.log
您在 Node 应用中使用的常用 API。
重构路线
现在,在functions/heroes-get/index.ts文件中将路由指向该服务。打开该文件,并将其替换为以下代码。
import { AzureFunction, Context, HttpRequest } from '@azure/functions';
import { heroService } from '../services';
const httpTrigger: AzureFunction = async function(context: Context, req: HttpRequest): Promise<void> {
await heroService.getHeroes(context); // 👈 This calls the hero service
};
export default httpTrigger;
您添加的代码调用异步函数heroService.getHeroes
并传入context
包含请求和响应对象的内容。
创建剩余函数
请记住,Express 应用中总共有 8 个端点,我们刚刚创建了第一个。现在,请按照以下步骤为其余端点创建 Azure 函数。
- 按F1打开命令面板
- 键入并选择Azure Functions: 创建函数
- 选择HTTP 触发器作为函数类型
- 输入英雄和恶棍的函数名称。我推荐heroes-get、heroes-post、heroes-put、heroes-delete、Villains-get、Villains-post、Villains-put、Villains-delete。
- 选择匿名作为身份验证级别
- 打开function.json并将方法设置为 get、post、put 或 delete 的适当值。
- 在绑定部分中,对于get和post,添加一个
route: "heroes"
条目(或根据需要添加恶棍)。 - 在绑定部分中,对于删除和放置,添加一个
route: "heroes/{id}"
条目(或根据需要添加恶棍)。 - 在每个函数的index.ts文件中添加代码以调用适当的英雄或恶棍服务函数。
查看 Functions App
Azure Functions 应用程序现在具有映射到其适当端点的文件夹,如下所示。
方法 | 路线终点 | 文件夹 |
---|---|---|
得到 | 英雄 | 英雄获得 |
邮政 | 英雄 | 英雄帖 |
放 | 英雄:ID | 英雄放 |
删除 | 英雄/:id | 英雄删除 |
得到 | 恶棍 | 恶棍得到 |
邮政 | 恶棍 | 恶棍邮报 |
放 | 恶棍:id | 恶棍放 |
删除 | 恶棍/:id | 恶棍删除 |
函数文件夹中包含的 Azure 函数应用程序的结构应如下所示。
functions
| - heroes-delete
| | - function.json
| | - index.ts
| - heroes-get
| | - function.json 👈 The hero route's configuration
| | - index.ts 👈 The hero routes
| - heroes-post
| | - function.json
| | - index.ts
| - heroes-put
| | - function.json
| | - index.ts
| - services 👈 The same folder that the Express app has
| | - data.ts 👈 The hero data (could be database API calls)
| | - hero.service.ts 👈 The logic to get the hero data
| | - index.ts
| | - villain.service.ts
| - villains-delete
| | - function.json
| | - index.ts
| - villains-get
| | - function.json
| | - index.ts
| - villains-post
| | - function.json
| | - index.ts
| - villains-put
| | - function.json
| | - index.ts
| - .funcignore
| - .gitignore
| - host.json
| - local.settings.json
| - package.json
| - proxies.json
| - tsconfig.json
调试 Node Express 和 Angular
现在是时候运行应用程序并查看它是否一切正常了!我们将通过 VS Code 调试器执行此操作。
为了保持独立,我们将确保 Express 应用使用端口7070,Azure Functions 应用使用端口7071。如果我们真的要移除 Express 应用(目前我们完全可以移除),我们可以保留相同的端口。但出于教学目的,我们还是保留这两个应用。
- 打开proxy.conf.json并将端口更改为7071(我们的功能应用程序)
- 打开 VS Code 命令面板F1
- 输入View: Show Debug并按ENTER
- 选择调试功能和 Angular
- 按F5
- 打开浏览器访问http://localhost:7071
您现在可以在函数和 Angular 代码中设置断点。
如果你错过了,.vscode/launch.json和.vscode/tasks.json文件是本项目调试体验不可或缺的一部分。我鼓励你探索这些文件,并根据自己的需求复制/重构它们的内容。
可选 - 删除 Express App
目前 Express 应用已不再使用。您可以随时删除它(您可以随时重新克隆 GitHub 示例),或者如果您想在 Express 和 Azure Functions 之间切换,也可以保留它。
概括
最终结果是我们有了 Angular 和 Azure Functions。现在我们可以少考虑服务器了(明白了吗,因为我们用的是无服务器?)。
Node 和 Express 一直以来都非常强大,常用于提供 API 端点服务。现在,有了无服务器,您可以迁移 API,无需担心服务器的设置或维护,甚至可能降低始终在线服务器的成本,并用 Azure Functions 服务取代 Express 服务器。您的努力将带来一个可扩展的 API,让您专注于代码,而不是服务器。
如果要将 Azure Functions 应用部署到云端,可以按照本教程进行部署。您只需要一个Azure 帐户,然后使用适用于 Visual Studio Code 的 Azure Functions 扩展进行部署。
示例项目的完整解决方案位于 GitHub 上。README 文件中也提供了入门说明。您可以尝试运行 Express 应用或 Azure Functions 应用来了解差异。然后尝试将同样的转换应用到您的代码中。
资源
这里有大量与本文所涵盖主题相关的资源。
VS 代码
Azure 函数
- Azure Functions local.settings.json文件
- 使用 Azure Functions 部署到 Azure 的教程
- 有关Azure Functions TypeScript 支持的文章