在本教程中,我将向你展示我最喜欢的部署数据库驱动的Web应用程序的工作流程。它是为那些想在自己的副业上进行全栈式开发的开发者准备的,而不必建立和维护复杂的多服务基础设施。
我们将部署一个用Node.js和Express编写的非常简陋的网络应用。它允许访问者写和保存笔记,并阅读以前写的笔记。数据被存储在MongoDB数据库中。我们将使用GitHub Actions来创建一个CI/CD工作流,在AWS Lambda上部署我们的应用程序。
重点是简单、实用主义和节约成本。由于AWS和MongoDB有非常慷慨的免费层级,你可以免费跟随。不过要记住,如果你不想最终支付几分钱的话,事后要取消部署应用程序。由于你的应用程序将是公开的,从理论上讲,其使用量可以超过免费层。然而,如果你打算为自己的目的扩展这个应用程序,我可以推荐这个设置,因为对于一个具有中等流量的网站来说,它是非常实惠的。
你可以在我们的GitHub账户上找到本教程的所有代码。
前提条件
你需要一些东西来构建这个应用程序。确保你的系统中安装了Node和Docker。要安装Node,你可以使用Node版本管理器(nvm)(见这里的一些说明)。对于Docker,为你的操作系统安装最新版本的Docker Desktop。
注意,我们将使用Docker在我们的机器上运行MongoDB的一个实例。另外,你也可以手动安装MongoDB社区版。你可以在这里找到一些说明。
你还需要在GitHub、MongoDB和亚马逊网络服务(AWS)拥有账户。在AWS上注册时,你必须输入一个信用卡号码。如上所述,采取本教程中的步骤不会超过免费层。
以前在Node和Express方面的一些知识可能会有帮助。
本地开发
好了,让我们开始吧。我们首先需要一个有新的package.json 文件的空文件夹。如果你执行npm init ,你可以创建一个。
我们需要安装以下依赖项。
- express,对来自客户端的HTTP请求做出反应
- mongoose,用于与我们的MongoDB数据库通信
- aws-serverless-express,用于使AWS Lambda能够调用我们的应用程序
- concurrently(作为开发依赖),以并行执行npm脚本。
运行以下命令来安装它们。
npm install --save express mongoose aws-serverless-express && npm install --save-dev concurrently
1.MongoDB和mongoose
由于我们使用MongoDB数据库来存储数据,在本地机器上运行一个数据库实例对开发很有帮助。这就是我们使用最新的mongoDocker镜像的地方。如果你在你的机器上安装了Docker,这就像在你的终端输入docker run mongo 一样简单。镜像会从dockerhub拉过来,在一个新的容器中启动。如果你对Docker不熟悉,那也没关系。你需要知道的是,你的电脑上有一个MongoDB实例在运行,你可以与之通信。
为了使我们的应用程序能够与数据库通信,我们需要初始化一个连接。我们在一个名为mongoose.js 的新文件中这样做。Mongoose是一个帮助我们进行MongoDB对象建模的库。
// mongoose.js
const mongoose = require("mongoose");
const uri = process.env.MONGODB_URL;
let connection;
const connect = async () => {
try {
connection = await mongoose.createConnection(uri, {
useNewUrlParser: true,
useFindAndModify: false,
useUnifiedTopology: true,
bufferCommands: false, // Disable mongoose buffering
bufferMaxEntries: 0, // and MongoDB driver buffering
});
return connection;
} catch (e) {
console.error("Could not connect to MongoDB...");
throw e;
}
};
function getConnection() {
return connection;
}
module.exports = { connect, getConnection };
这个文件导出了一个带有两个函数的对象。connect() ,在我们在环境变量中指定的位置创建一个与MongoDB的连接。该连接被存储在一个名为connection 的变量中。getConnection() 只是返回连接变量。你可能想知道为什么我们不直接返回连接变量本身。这是由于Node.js在首次加载所需的模块后会进行缓存。因此,我们使用一个函数从我们的mongoose.js 模块中拉出最新的连接变量。
现在,我们的应用程序将能够连接到数据库,我们也想在其中存储数据--更具体地说,就是我们可以在用户界面上写的笔记。因此,我们将为我们的笔记创建一个数据模型。这将在models 文件夹中的一个名为Notes.js 的新文件中完成。
// models/Notes.js
const mongoose = require("mongoose");
const { getConnection } = require("../mongoose");
const conn = getConnection();
const Schema = mongoose.Schema;
module.exports = conn.model(
"Note",
new Schema({ text: { type: String, required: true } })
);
在这里,我们从我们的mongoose.js 模块中拉出当前的连接,并在其上注册一个叫做Note 的模型。它有一个非常基本的模式,只包含一个字符串类型的必要属性text 。有了这个模型,我们可以构建存储在数据库中的文档。
2.Express应用程序
接下来,我们创建一个简单的Express应用程序。在你的项目根部创建一个名为app.js 的文件。它有以下内容。
// app.js
const express = require("express");
const app = express();
app.use(express.urlencoded({ extended: false }));
app.get("/", async (req, res) => {
try {
const Note = require("./models/Note");
const notes = await Note.find({});
return res.status(200).send(
`<!DOCTYPE html>
<html lang="en">
<head>
<title>My Notes</title>
<style>
html {
text-align: center;
background-color: #93c5fd;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
color: white;
font-size: 2rem;
}
textarea {
resize: none;
border: 2px solid #9ca3af;
border-radius: 4px;
background-color: #f3f4f6;
padding: 0.5rem;
width: 90%;
}
button {
padding-left: 2rem;
padding-right: 2rem;
padding-top: 7px;
padding-bottom: 7px;
background-color: #f3f4f6;
border: 2px solid #9ca3af;
color: #4b5563;
border-radius: 4px;
}
p {
border-bottom: 2px solid;
padding: 1rem;
text-align: left;
}
</style>
</head>
<body>
<h1>My Notes</h1>
<form method="POST">
<textarea required name="text" rows="5" cols="50" placeholder="Create a new note"></textarea>
<button type="submit">Save</button>
</form>
${notes.map((n) => `<p>${n.text}</p>`).join("")}
</body>
</html>`
);
} catch (e) {
return res.send(e);
}
});
app.post("/", async (req, res) => {
try {
const Note = require("./models/Note");
const note = new Note(req.body);
await note.save();
return res.send("Note saved. <a href=''>Refresh</a>");
} catch (e) {
return res.send(e);
}
});
module.exports = app;
正如我所说,这个应用程序是非常初级的,作为一个演示。首先,我们启动一个Express应用程序。然后,我们告诉它用内置的、经过编码的中间件来解析进入的请求体,以便我们能够处理提交的表单数据。该应用程序有两个方法处理程序,用于处理应用程序根上的请求。
-
app.get("/", ...)处理HTTP GET请求。它在我们的用户加载页面时被调用。我们想向他们展示的是一个简单的页面,他们可以在这里输入一个笔记并保存它。此外,我们还想显示以前写的笔记。在请求处理程序的回调函数中,我们需要我们的 模型。该模型必须在我们的POST请求处理程序的回调函数中被要求,因为它需要一个当前的数据库连接--当 文件第一次被加载时,它可能不存在。然后,我们应用 方法来接收来自数据库的所有笔记。这个方法返回一个承诺。因此,我们要等待它的解决。最后但并非最不重要的是,我们使用响应对象的 方法( )来发送一个字符串回给客户端。这个字符串包含HTML语法,浏览器会将其渲染成实际的HTML元素。对于我们数据库中的每个笔记,我们只需添加一个包含其文本的段落元素。Noteapp.jsfindsend``res这就是你可以把这个非常初级的例子转化为一个漂亮的用户界面的地方。你可以自由选择向客户发送什么。例如,这可以是一个完全捆绑的客户端React应用程序。你也可以选择一种服务器端渲染的方法--例如,通过使用像handlebars这样的Express视图引擎。根据它是什么,你可能要在你的应用程序中添加更多的路由,并提供像JS捆绑的静态文件。
-
app.post("/", ...)处理HTTP POST请求。它在用户保存他们的笔记时被调用。同样,我们首先需要我们的 模型。请求的有效载荷可以通过请求对象的body属性访问( )。它包含我们的用户提交的文本。我们用它来创建一个新的文档,并用Mongoose提供的 方法来保存它。同样,我们等待这个异步操作完成后再通知用户,让他们有机会刷新页面。Note``reqsave
为了让我们的应用程序真正开始监听HTTP请求,我们必须调用Express提供的listen 方法。我们将在一个单独的名为dev.js 的文件中完成这项工作,并将其添加到我们的项目根中。
// dev.js
const app = require("./app");
const { connect } = require("./mongoose");
connect();
const port = 4000;
app.listen(port, () => {
console.log(`app listening on port ${port}`);
});
在这里,我们从我们的mongoose.js 文件中调用connect 函数。这将启动数据库连接。最后但同样重要的是,我们开始监听4000端口的HTTP请求。
用两个单独的命令来启动mongo Docker镜像和我们的应用程序是有点麻烦的。因此,我们在我们的package.json 文件中添加了一些脚本。
"scripts": {
"start": "concurrently 'npm:mongoDB' 'npm:dev'",
"dev": "MONGODB_URL=mongodb://localhost:27017 node dev.js",
"mongoDB": "docker run -p 27017:27017 mongo"
}
mongoDB 启动MongoDB实例,并将容器端口27017映射到我们本地机器的端口27017。 ,启动我们的应用程序,并设置环境变量 ,该变量在 文件中加载,以便与我们的数据库通信。 脚本并行地执行这两个脚本。现在,我们需要做的就是在终端运行 ,来启动我们的应用程序。dev MONGODB_URL mongoose.js start npm start
现在你可以通过访问浏览器中的http://localhost:4000来加载应用程序。
继续阅读《使用Express和MongoDB的无服务器部署指南》,请访问SitePoint。