使用 Express 和 Mongodb 的无服务器 API 速成课程
关于如何创建无服务器 API 并将其部署到 AWS Lambda 的快速简便教程。持久数据存储在 Atlas 集群的 MongoDB 中。点击此处查看完整教程。
无服务器已经成为各种用例的绝佳工具。数据处理器、聊天机器人、API,等等,现在都可以使用无服务器架构进行开发。
今天,我将带您了解如何创建一个可在 AWS Lambda 上运行、并基于持久化 MongoDB 数据存储的生产级 Express API。没错,您可以在 AWS Lambda 上构建 Express 应用。太棒了!而且,您可以轻松使用 MongoDB!
仔细想想,其实很简单。使用 AWS Lambda 和使用一个小型 Node.js 运行时几乎一样。它只是抽象了除代码之外的所有内容。
让我们开始吧。
您可以严重伤害我的感情并跳到您感兴趣的部分,或者继续阅读。
设置本身将非常简洁。但它仍然包含您为未来生产应用继续添加功能所需的一切。以下是最终布局的示意图,您可以大致了解。
可以看出,这是一个相当简单的笔记 API,带有 CRUD 逻辑,但它能完成任务。闲话少叙,让我们开始启动项目吧。
首先,您需要安装并配置无服务器框架。它是一个简单的CLI工具,可以使开发和部署变得非常容易。
$ npm i -g serverless
现在,您已在计算机上全局安装了 Serverless 框架。现在,您可以从终端中的任何位置使用 Serverless 命令。
注意:如果您使用的是 Linux,则可能需要以 sudo 身份运行该命令。
打开你的 AWS 控制台,点击左上角的服务下拉菜单。你会看到一大堆服务出现。在搜索框中输入“IAM”,然后点击它。
您将被重定向到您帐户的 IAM 主页。继续添加新用户。
为您的 IAM 用户命名,并勾选“编程访问”复选框。继续下一步。
现在,您可以为用户添加一组权限。由于我们将让 Serverless 在我们的 AWS 账户上创建和删除各种资产,因此请检查 AdministratorAccess。
继续下一步,您将看到用户已创建。现在,也只有现在,您才有权访问用户的访问密钥 ID和秘密访问密钥。请务必记下它们或下载 .csv 文件。请妥善保管,切勿向任何人展示。虽然这只是一个演示,但我已将它们像素化,以确保您了解保护它们安全的重要性。
完成后,我们终于可以继续将密钥输入无服务器配置中。
太棒了!保存密钥后,您就可以设置 Serverless 来访问您的 AWS 账户了。切换回终端,在一行中输入以下代码:
$ serverless config credentials --provider aws --key xxxxxxxxxxxxxx --secret xxxxxxxxxxxxxx
按下回车键!现在,你的 Serverless 安装知道在运行任何终端命令时要连接到哪个账户了。让我们开始实际操作吧。
创建一个新目录来存放您的无服务器应用服务。在那里启动一个终端。现在,您可以创建新服务了。
您可能会问,什么是服务?可以将其视为一个项目。但实际上并非如此。服务是定义 AWS Lambda 函数、触发这些函数的事件以及它们所需的任何 AWS 基础设施资源的地方,所有这些都包含在一个名为serverless.yml的文件中。
返回终端类型:
$ serverless create --template aws-nodejs --path sls-express-mongodb
create 命令会创建一个新的service。震惊!但接下来才是有意思的部分。我们需要为函数选择一个运行时。这被称为template。传入 templateaws-nodejs
会将运行时设置为 Node.js。这正是我们想要的。path会为该服务创建一个文件夹。在本例中,我们将其命名为sls-express-mongodb。
使用您喜欢的代码编辑器打开sls-express-mongodb文件夹。里面应该有三个文件,但现在我们只关注serverless.yml文件。它包含此服务的所有配置设置。在这里,您可以指定常规配置设置和每个函数的配置设置。您的serverless.yml 文件将充满样板代码和注释。您可以随意删除它们并粘贴此文件。
# serverless.yml
service: sls-express-mongodb
custom:
secrets: ${file(secrets.json)}
provider:
name: aws
runtime: nodejs8.10
stage: ${self:custom.secrets.NODE_ENV}
region: eu-central-1
environment:
NODE_ENV: ${self:custom.secrets.NODE_ENV}
DB: ${self:custom.secrets.DB}
functions:
app:
handler: server.run
events:
- http:
path: /
method: ANY
cors: true
- http:
path: /{proxy+}
method: ANY
cors: true
plugins:
- serverless-offline
该functions
属性列出了服务中的所有函数。但我们只需要一个函数,因为整个 Express 应用将被打包到这个函数中。处理程序会引用该函数。最终的应用将包含一个server.js
包含该函数的文件run
。非常简单。
现在看看这些事件。它们充当了代理的角色。这意味着所有到达 HTTP 端点的请求都会被代理到内部的 Express 路由器中。很酷。
我们在顶部还有一个custom
部分。这可以安全地将环境变量加载到我们的应用中。稍后可以使用 来引用它们,${self:custom.secrets.<environment_var>}
实际值保存在一个名为 的简单文件中secrets.json
。
最后,我们还有用于serverless-offline
离线测试的插件。
准备好进行更多配置了吗?是的,没人喜欢这部分。不过,请耐心等待。跳转到MongoDB Atlas 并注册。
它是免费的,无需信用卡。这将是我们进行实际操作所需的沙盒。设置好帐户后,打开帐户页面并添加新组织。
选择一个你喜欢的名称,任何名称都可以。点击“下一步”,继续创建组织。
很好。这会带你进入组织页面。点击“新建项目”按钮。
这将打开一个页面来命名你的项目。只需输入你喜欢的名称,然后点击“下一步”。
MongoDB 非常重视权限和安全,因此 Atlas 会显示另一个权限管理页面。我们暂时跳过该页面,直接创建项目即可。
呼,搞定了!终于可以创建真正的集群了!点击巨大的绿色“构建新集群”按钮。这会打开一个巨大的集群创建窗口。您可以保留所有默认设置,只需确保选择M0实例大小并禁用备份即可。如您所见,此集群的价格为免费。太棒了!就这样,点击“创建集群”。
完成所有操作后,为集群添加一个管理员用户并给他一个非常强的密码。
现在,您只需启用从任何地方的访问即可。前往 IP 白名单。
您的集群部署需要几分钟时间。在此期间,我们先安装一些依赖项。
这绝对是我所有项目中最喜欢的部分……没人这么说过。不过,我们得确保这一步做得正确,这样才能顺利完成接下来的工作。
$ npm init -y
$ npm i --save express mongoose body-parser helmet serverless-http
$ npm i --save-dev serverless-offline
首先,我们要安装生产依赖项,你肯定了解 Express、Mongoose 和 BodyParser。Helmet 是一个小型中间件,用于使用适当的 HTTP 标头保护你的端点。然而,真正的强大之处在于无服务器 HTTP 模块。它会在 Express 应用程序中创建代理,并将其打包成一个 lambda 函数。
最后,我们需要使用 Serverless Offline 在本地测试我们的应用。现在我们终于可以开始写代码了。
是时候了!事不宜迟,让我们开始吧。
首先,我们需要将handler.js
文件重命名为server.js
。这里我们只放置使用模块运行 lambda 函数的逻辑serverless-http
。
// server.js
const sls = require('serverless-http')
const app = require('./lib/app')
module.exports.run = sls(app)
如你所见,我们需要serverless-http
,并导出一个名为 的函数run
。它将保存实例的值,serverless-http
并将我们的应用作为参数传递。这就是我们将 Express 应用打包成 lambda 函数所需的一切!简直太简单了。
接下来创建secrets.json
文件来保存环境变量。
// secrets.json
{
"NODE_ENV": "dev",
"DB": "mongodb://<user>:<password>@<clustername>.mongodb.net:27017,<clustername>.mongodb.net:27017,<clustername>.mongodb.net:27017/<database>?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin&retryWrites=true"
}
要获取 Atlas 集群的连接字符串,请导航到集群仪表板并按灰色的连接按钮。按照说明操作,并确保 URL 看起来与上面的字符串类似。
现在我们可以开始编写实际的 Express 应用程序了。
在根目录中创建一个名为 的新文件夹lib
。在这里,您需要创建一个app.js
文件和db.js
一个文件作为开始。
// ./lib/db.js
const mongoose = require('mongoose')
mongoose.connect(process.env.DB)
安装后,mongoose
连接数据库的过程会大大简化。这就是我们所需要的。
注意:process.env.DB
在 中设置secrets.json
并在 中引用serverless.yml
。
将开关添加db.js
到app.js
文件后,粘贴下面的代码片段。
// ./lib/app.js
const express = require('express')
const app = express()
const bodyParser = require('body-parser')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
const helmet = require('helmet')
app.use(helmet())
require('./db')
const routes = require('./routes')
app.use('/api', routes)
module.exports = app
如果你曾经用 Express 写过代码,你一定会觉得这很熟悉。我们引入了所有模块,使用了中间件,引入了上面刚刚创建的数据库连接,并将路由绑定到/api
路径上。但是我们还没有创建任何路由。好吧,那就开始吧!
在文件夹中lib
,创建一个名为 的新文件夹routes
。它将作为应用中所有路由的基础。index.js
在该文件夹中创建一个文件routes
,并将此代码片段粘贴到其中。
// ./lib/routes/index.js
const express = require('express')
const router = express.Router()
const notes = require('./notes/notes.controller')
router.use('/notes', notes)
// Add more routes here if you want!
module.exports = router
现在我们只需将任何额外的路由添加到此文件即可,无需修改任何其他内容。这太简单了。
终于到了最精彩的部分了。正如你在index.js
上面的文件中看到的,我们需要导入一个notes.controller.js
文件,用于定义 CRUD 操作。好了,让我们开始创建它吧!
不过,为了避免跑题,我们首先需要一个 Notes API 的模型。notes
在routes
文件夹中创建一个文件夹,并在其中创建两个名为note.js
和 的文件notes.controller.js
。它们note.js
将包含我们笔记的模型定义。就像这样。
// ./lib/routes/notes/note.js
const mongoose = require('mongoose')
const NoteSchema = new mongoose.Schema({
title: String,
// this is a bug in the markdown - should not have the quotes ""
description: String
})
module.exports = mongoose.model('Note', NoteSchema)
对于这个示例来说,只需要一个标题和描述就足够了。接下来,我们准备添加 CRUD。打开notes.controller.js
并粘贴以下内容。
// ./lib/routes/notes/notes.controller.js
const express = require('express')
const notesController = express.Router()
const Note = require('./note')
notesController
.post('/', async (req, res, next) => {
const note = await Note.create(req.body)
res.status(200).send(note)
})
notesController
.put('/:id', async (req, res, next) => {
const note = await Note.findByIdAndUpdate(req.params.id, { $set: req.body }, { $upsert: true, new: true })
res.status(200).send(note)
})
notesController
.get('/', async (req, res, next) => {
const notes = await Note.find()
res.status(200).send(notes)
})
notesController
.get('/:id', async (req, res, next) => {
const note = await Note.findById(req.params.id)
res.status(200).send(note)
})
notesController
.delete('/:id', async (req, res, next) => {
const note = await Note.deleteOne({ _id: req.params.id })
res.status(200).send(note)
})
module.exports = notesController
别忘了在文件顶部引用 Note 模型。除此之外,一切都相当简单。我们使用常用的 Mongoose 模型方法来创建 CRUD 操作,当然,它的语法也非常棒async/await
。你还可以考虑在await
操作符周围添加 try-catch 代码块。不过,这个简单的例子就足够了。
代码就这些了。准备测试!
我习惯在部署应用之前先在本地进行测试。因此,我将快速向您介绍如何使用 进行测试serverless-offline
。由于您已经安装并添加到plugins
部分,serverless.yml
您只需运行一个命令即可在本地计算机上启动 API 网关和 AWS Lambda 的本地仿真。
$ sls offline start --skipCacheInvalidation
注意:在项目的根目录中运行sls
,您应该会看到一个命令列表。如果您配置正确,sls offline
应该sls offline start
可以使用。
为了让您更轻松地使用此命令,请随意将其作为 npm 脚本添加到package.json
.
// package.json
{
"name": "a-crash-course-on-serverless-apis-with-express-and-mongodb",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"offline": "sls offline start --skipCacheInvalidation"
// right here!
},
"keywords": [],
"author": "Adnan Rahić",
"license": "ISC",
"dependencies": {
"body-parser": "^1.18.3",
"express": "^4.16.3",
"helmet": "^3.12.1",
"mongoose": "^5.1.7",
"serverless-http": "^1.5.5"
},
"devDependencies": {
"serverless-offline": "^3.20.2"
}
}
添加后,您可以npm run offline
改为运行该命令。这样更简洁,也更容易记住。回到终端,继续运行它。
$ npm run offline
您将看到终端告诉您本地服务器已在端口 3000 上启动。让我们测试一下!
为了测试我的端点,我通常使用 Insomnia 或 Postman,但您可以随意使用任何您喜欢的工具。首先,点击 POST 端点添加一条注释。
太棒了!一切正如预期。接下来尝试 GET 请求。
一切运行如梦似幻。现在,继续尝试所有其他端点。确保它们都能正常工作,然后,我们就可以准备将其部署到 AWS 了。
如果我告诉你部署这个 API 只需要运行一个命令,你会相信吗?嗯,确实如此。
$ sls deploy
回到终端,运行上面的命令并耐心等待。你会看到终端中出现了几个端点。这些就是你的 API 的端点。
按照我上面展示的方法,再次测试这些已部署的端点,确保它们能够正常工作。
接下来,你可能会注意到你只将 API 部署到了dev
stage 版本。这还不够。我们需要修改NODE_ENV
并部署到生产环境。打开secrets.json
文件,将第二行更改为:
"NODE_ENV": "production",
这将传播并设置您的 Express API 的环境production
以及stage
生产环境。在部署生产 API 之前,我们只需删除该node_modules
文件夹并重新安装所有带有该--production
标志的模块即可。
$ rm -rf ./node_modules && npm i --production
这将确保仅安装列表中指定的依赖项dependencies
,package.json
而不安装列表中的依赖项devDependencies
。
在部署之前,您只需注释掉 中的插件部分serverless.yml
。
# serverless.yml
service: sls-express-mongodb
custom:
secrets: ${file(secrets.json)}
provider:
name: aws
runtime: nodejs8.10
stage: ${self:custom.secrets.NODE_ENV}
region: eu-central-1
environment:
NODE_ENV: ${self:custom.secrets.NODE_ENV}
DB: ${self:custom.secrets.DB}
functions:
app:
handler: server.run
events:
- http:
path: /
method: ANY
cors: true
- http:
path: /{proxy+}
method: ANY
cors: true
# comment this out
# plugins:
# - serverless-offline
继续使用与上面相同的命令进行部署。
$ sls deploy
如果我们不进行任何负载测试,那么这不会是一个合适的生产 API 设置教程。我倾向于使用一个很小的 npm 模块来进行负载测试。它叫做loadtest,可以用一个简单的命令安装。
$ npm i -g loadtest
注意:Linux 用户需要在命令前加上前缀sudo
。
让我们慢慢开始。我们要运行的命令是,/api/notes
在 10 个并发用户的情况下,向该路径发送 100 次 GET 请求。
$ loadtest -n 100 -c 10 https://<id>.execute-api.eu-central-1.amazonaws.com/production/api/notes
处理所有这些请求大约花了 5 秒钟,而且一切顺利。您可以放心,无论您最终拥有的 API 规模如何,它都会自动扩展到您需要的大小,并顺利地为您的用户提供服务。
所有无服务器应用程序的首要问题是其分布式特性。简而言之,想要全面了解所有正在发生的事情极其困难。更不用说当出现问题时,调试起来有多困难了。
为了平息我的恐惧,我使用了Tracetest。它提供由 OpenTelemetry 支持的端到端测试和调试。
幸好,这里有详细的文档,让新手入门变得轻而易举。请按照快速入门指南操作。不过,别忘了回来这里看看。😄
这是一段充满冒险的旅程!您创建了一个可用于生产的无服务器 API。使用无服务器架构可能会令人感到畏惧。主要是因为您不熟悉一些服务,例如 Lambda 和 API Gateway。
上面展示的方法是我通常的做法。使用 Node.js 以及你熟悉的框架、模块和中间件,可以更轻松地过渡到无服务器模式。
幸运的是,我们拥有像Serverless Framework这样的开发工具和像Tracetest这样的可观察性工具,这使得成为开发人员变得非常容易。
如果您错过了上述任何步骤,这里是包含所有代码的存储库。
关于如何创建无服务器 API 并将其部署到 AWS Lambda 的快速简便教程。持久数据存储在 Atlas 集群的 MongoDB 中。点击此处查看完整教程。
如果您想阅读我之前的一些无服务器思考,请转到我的个人资料或加入我的时事通讯!
或者,立即查看我的几篇文章:
希望大家喜欢阅读这篇文章,就像我喜欢写这篇文章一样。如果你们喜欢,请点个小爱心,这样 dev.to 上会有更多人看到这篇教程。下次再见,保持好奇心,享受乐趣吧。
免责声明:Tracetest赞助了这篇博文。使用可观察性可以将测试创建和故障排除的时间和精力减少 80%。