Ubuntu中将MongoDB集成到你的Node.js应用

引言

在开发 Node.js 项目时,你可能需要存储和查询数据。这时,你需要选择一个适合你的应用数据和查询类型的数据库解决方案。

本教程将指导你如何将 MongoDB 数据库集成到现有的 Node 应用中。如果你的数据需求包括可扩展性和灵活性,那么像 MongoDB 这样的 NoSQL 数据库可能会很有用。MongoDB 与 Node 集成良好,因为它被设计为可以异步地与 JSON 对象一起工作。

前提条件

在开始本教程之前,

需要一个已设置的Ubuntu服务器,具有非root用户,具有sudo权限,并启用了防火墙以阻止非必要的端口。

设置完成后,以的非root用户身份登录并进行第一步。

建议使用至少有以下配置的服务器:

4 核心的 CPU,4GB 的内存

选择服务器提供商
为了本教程的演示,我将以一个具体的云服务提供商为例,展示如何进行操作。选择哪个提供商根据个人偏好和需求来决定。

以下步骤仅供参考,请根据实际需求选择配置。

购买云服务器
本示例中,我们选择了香港作为服务器区域。

点击 云产品云服务器立即购买

选择操作系统
在创建服务器实例时,选择 Ubuntu 作为操作系统。

连接到服务器
使用 X-shell 或偏好的 SSH 客户端,通过远程用户名和密码连接到服务器。成功连接后,将看到特定的欢迎信息,表明已成功登录。

https://syxoss.oss-cn-hangzhou.aliyuncs.com/Typora202411211021991.png

  • 你的机器或服务器上安装了 Node.js 和 npm,安装 NodeSource 管理的 PPA。
  • 你的机器或服务器上安装了 MongoDB。

第一步 — 创建 Mongo 用户

在我们开始处理应用代码之前,我们将创建一个可以访问我们应用数据库的管理员用户。这个用户将对任何数据库拥有管理员权限,这将给你提供灵活性,以便根据需要切换和创建新数据库。

首先,检查 MongoDB 是否在你的服务器上运行:

sudo systemctl status mongodb

以下输出表明 MongoDB 正在运行:

Output
● mongodb.service - An object/document-oriented database
   Loaded: loaded (/lib/systemd/system/mongodb.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2019-01-31 21:07:25 UTC; 21min ago
...

接下来,打开 Mongo shell 来创建你的用户:

mongo

这将带你进入一个管理 shell:

Output
MongoDB shell version v3.6.3
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.3
...
>

当你打开 shell 时,由于你对 admin 数据库的无限制访问,你会看到一些管理警告。你可以阅读如何在 Ubuntu 16.04 上安装和保护 MongoDB,以了解如何在生产设置中限制这种访问。

目前,你可以使用你对 admin 数据库的访问来创建一个具有 userAdminAnyDatabase 权限的用户,这将允许对你的应用数据库进行密码保护访问。

在 shell 中,指定你想要使用 admin 数据库来创建你的用户:

use admin

接下来,通过添加用户名和密码来创建角色和密码,使用 db.createUser 命令。输入此命令后,shell 会在每行前添加三个点,直到命令完成。确保替换这里提供的用户名和密码为你自己的:

db.createUser(
  {
    user: "sammy",
    pwd: "your_password",
    roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
  }
)

这会在 admin 数据库中为用户 sammy 创建一个条目。你选择的用户名和 admin 数据库将作为你用户的标识符。

整个过程的输出将如下所示,包括表明条目成功的消息:

Output
> db.createUser(
...  {
...    user: "sammy",
...    pwd: "your_password",
...    roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
...  }
...)
Successfully added user: {
        "user" : "sammy",
        "roles" : [
                {
                        "role" : "userAdminAnyDatabase",
                        "db" : "admin"
                }
        ]
}

创建了你的用户和密码后,你可以退出 Mongo shell:

exit

现在你已经创建了数据库用户,可以继续克隆启动项目代码并添加 Mongoose 库,这将允许你为数据库中的集合实现模式和模型。

第二步 — 添加 Mongoose 和数据库信息到项目

接下来的步骤是克隆应用启动代码,并将 Mongoose 和 MongoDB 数据库信息添加到项目中。

在你的非 root 用户的主目录中,从 GitHub 账户克隆 nodejs-image-demo 仓库。这个仓库包含了在如何使用 Docker 构建 Node.js 应用中描述的设置代码。

将仓库克隆到一个名为 node_project 的目录中:

git clone <https://github.com/do-community/nodejs-image-demo.git> node_project

切换到 node_project 目录:

cd  node_project

在修改项目代码之前,让我们使用 tree 命令查看项目的结构。

提示: tree 是一个有用的命令,用于从命令行查看文件和目录结构。你可以使用以下命令安装它:

sudo apt install tree

使用它,cd 进入给定目录并输入 tree。你也可以通过命令如:

tree /home/sammy/sammys-project

提供起始点的路径。

node_project 目录中输入以下命令查看结构:

tree

当前项目的結構如下:

Output
├── Dockerfile
├── README.md
├── app.js
├── package-lock.json
├── package.json
└── views
    ├── css
    │   └── styles.css
    ├── index.html
    └── sharks.html

我们将在本教程中向该项目添加目录,tree 将是一个有用的命令,帮助我们跟踪我们的进度。

接下来,使用 npm install 命令将 mongoose npm 包添加到项目中:

npm install mongoose

此命令将在项目目录中创建一个 node_modules 目录,使用项目 package.json 文件中列出的依赖项,并在该目录中添加 mongoose。它还会将 mongoose 添加到你的 package.json 文件中列出的依赖项中。有关 package.json 的更详细讨论,请参见如何使用 Docker 构建 Node.js 应用的第一步。

在创建任何 Mongoose 模式或模型之前,我们将添加数据库连接信息,以便我们的应用能够连接到数据库。

为了尽可能地分离你的应用关注点,创建一个名为 db.js 的单独文件,用于你的数据库连接信息。你可以使用 nano 或你喜欢的编辑器打开这个文件:

nano db.js

首先,使用 require 函数导入 mongoose 模块:

~/node_project/db.js

const mongoose = require('mongoose');

这将让你访问 Mongoose 的内置方法,你将使用它们来创建数据库连接。

接下来,添加以下常量以定义 Mongo 的连接 URI 信息。尽管用户名和密码是可选的,但我们将包含它们,以便我们可以要求对数据库进行身份验证。确保替换下面列出的用户名和密码为你自己的信息,如果你愿意,可以给数据库起一个不同于 'sharkinfo' 的名字:

~/node_project/db.js

const mongoose = require('mongoose');

const MONGO_USERNAME = 'sammy';
const MONGO_PASSWORD = 'your_password';
const MONGO_HOSTNAME = '127.0.0.1';
const MONGO_PORT = '27017';
const MONGO_DB = 'sharkinfo';

由于我们在当地运行数据库,我们使用了 127.0.0.1 作为主机名。这在其他开发环境中会改变:例如,如果你使用单独的数据库服务器或在容器化工作流中使用多个节点。

最后,定义一个常量用于 URI 并使用 mongoose.connect() 方法创建连接:

~/node_project/db.js

...
const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;

mongoose.connect(url, {useNewUrlParser: true});

在 URI 中我们指定了用户的 authSourceadmin 数据库。这是必要的,因为我们在连接字符串中指定了用户名。使用 mongoose.connect() 时使用 useNewUrlParser 标志表示我们希望使用 Mongo 的新 URL 解析器。

保存并关闭文件,完成编辑。

作为最后一步,将数据库连接信息添加到 app.js 文件中,以便应用可以使用它。打开 app.js

nano app.js

文件的前几行看起来像这样:

~/node_project/app.js

const express = require('express');
const app = express();
const router = express.Router();

const path = __dirname + '/views/';
...

在常量定义下方router(靠近文件顶部)添加以下行:

~/node_project/app.js

const router = express.Router();
const db = require('./db');

const path = __dirname + '/views/';
...

这告诉应用使用 db.js 中指定的数据库连接信息。

保存并关闭文件,完成编辑。

有了数据库信息和添加到项目的 Mongoose,你准备好创建将塑造你的 sharks 集合中数据的模式和模型了。

第三步 — 创建 Mongoose 模式和模型

下一步是考虑用户将在 sharkinfo 数据库中创建的 sharks 集合的结构。我们希望这些创建的文档具有什么结构?鲨鱼信息页面包括一些关于不同鲨鱼及其行为的细节:

为了保持这个主题,我们可以让用户添加新鲨鱼,并提供有关它们行为的详细信息。这个目标将决定我们如何创建我们的模式。

为了保持你的模式和模型与应用的其他部分区分开来,在当前项目目录中创建一个 models 目录:

mkdir models

接下来,打开一个名为 sharks.js 的文件来创建你的模式和模型:

nano models/sharks.js

在文件顶部导入 mongoose 模块:

~/node_project/models/sharks.js

const mongoose = require('mongoose');

接下来,定义一个 Schema 对象作为你的鲨鱼模式的基础:

~/node_project/models/sharks.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

现在你可以定义你想要包含在模式中的字段。因为我们想要创建一个包含单个鲨鱼及其行为信息的集合,让我们包括一个 name 键和一个 character 键。在常量定义下方添加以下 Shark 模式:

~/node_project/models/sharks.js

...
const Shark = new Schema ({
        name: { type: String, required: true },
        character: { type: String, required: true },
});

这个定义包括了我们期望从用户那里获得的输入类型 —— 在这种情况下,是一个字符串 —— 以及该输入是否是必需的。

最后,使用 Mongoose 的 model() 函数创建 Shark 模型。这个模型将允许你查询集合中的文档并验证新文档。在文件底部添加以下行:

~/node_project/models/sharks.js

...
module.exports = mongoose.model('Shark', Shark)

最后一行使我们的 Shark 模型作为模块可用,使用 module.exports 属性。这个属性定义了模块将导出的值,使其在应用的其他部分可用。

完成的 models/sharks.js 文件如下所示:

~/node_project/models/sharks.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const Shark = new Schema ({
        name: { type: String, required: true },
        character: { type: String, required: true },
});

module.exports = mongoose.model('Shark', Shark)

保存并关闭文件,完成编辑。

有了 Shark 模式和模型,你可以开始处理将决定你的应用如何处理用户输入的逻辑。

第四步 — 创建控制器

下一步是创建控制器组件,它将决定用户输入如何保存到我们的数据库并返回给用户。

首先,创建一个目录用于控制器:

mkdir controllers

接下来,在该目录中打开一个名为 sharks.js 的文件:

nano controllers/sharks.js

在文件顶部,我们将导入我们的 Shark 模型模块,以便在我们的控制器逻辑中使用。我们还将导入 path 模块,以访问实用工具,这些实用工具将允许我们设置用户输入鲨鱼的表单路径。

添加以下 require 函数到文件的开头:

~/node_project/controllers/sharks.js

const path = require('path');
const Shark = require('../models/sharks');

接下来,我们将编写一系列函数,我们将使用控制器模块的 Node exports 快捷方式导出这些函数。这些函数将包括与我们用户鲨鱼数据相关的三个任务:

  • 向用户发送鲨鱼输入表单。
  • 创建新的鲨鱼条目。
  • 将鲨鱼展示回用户。

首先,创建一个 index 函数以显示带有输入表单的鲨鱼页面。在导入下方添加此函数:

~/node_project/controllers/sharks.js

...
exports.index = function (req, res) {
    res.sendFile(path.resolve('views/sharks.html'));
};

接下来,在 index 函数下方,添加一个名为 create 的函数,以在 sharks 集合中制作新的鲨鱼条目:

~/node_project/controllers/sharks.js

...
exports.create = function (req, res) {
    var newShark = new Shark(req.body);
    console.log(req.body);
    newShark.save(function (err) {
            if(err) {
            res.status(400).send('Unable to save shark to database');
        } else {
            res.redirect('/sharks/getshark');
        }
  });
               };

这个函数将在用户向 sharks.html 页面上的表单发布鲨鱼数据时被调用。我们将使用 POST 请求的 body,我们的 create 函数将使用我们导入的 Shark 模型制作一个新的鲨鱼文档对象,这里称为 newShark。我们添加了一个 console.log 方法来输出鲨鱼条目到控制台,以检查我们的 POST 方法是否按预期工作,但如果你喜欢,你可以选择省略它。

使用 newShark 对象,create 函数将调用 Mongoose 的 model.save() 方法,使用你在 Shark 模型中定义的键制作一个新的鲨鱼文档。这个回调函数遵循标准的 Node 回调模式:callback(error, results)。如果出现错误,我们将向用户发送错误报告的消息,如果成功,我们将使用 res.redirect() 方法将用户发送到将在浏览器中渲染他们的鲨鱼信息的端点。

最后,list 函数将向用户展示集合的内容。在 create 函数下方添加以下代码:

~/node_project/controllers/sharks.js

...
exports.list = function (req, res) {
        Shark.find({}).exec(function (err, sharks) {
                if (err) {
                        return res.send(500, err);
                }
                res.render('getshark', {
                        sharks: sharks
             });
        });
};

这个函数使用 Shark 模型和 Mongoose 的 model.find() 方法返回已输入 sharks 集合的鲨鱼。它通过返回查询对象 —— 在这种情况下,是 sharks 集合中的所有条目 —— 作为一个承诺,使用 Mongoose 的 exec() 函数。如果出现错误,回调函数将发送一个 500 错误。

返回的查询对象与 sharks 集合将在我们将在下一步中使用 EJS 模板语言创建的 getshark 页面中呈现。

完成的文件如下所示:

~/node_project/controllers/sharks.js

const path = require('path');
const Shark = require('../models/sharks');

exports.index = function (req, res) {
    res.sendFile(path.resolve('views/sharks.html'));
};

exports.create = function (req, res) {
    var newShark = new Shark(req.body);
    console.log(req.body);
    newShark.save(function (err) {
            if(err) {
            res.status(400).send('Unable to save shark to database');
        } else {
            res.redirect('/sharks/getshark');
        }
  });
               };

exports.list = function (req, res) {
        Shark.find({}).exec(function (err, sharks) {
                if (err) {
                        return res.send(500, err);
                }
                res.render('getshark', {
                        sharks: sharks
             });
        });
};

请记住,虽然我们这里没有使用箭头函数,但你可能希望在你自己的开发过程中包含它们。

保存并关闭文件,完成编辑。

在进入下一步之前,你可以再次运行 tree 从你的 node_project 目录查看项目的结构。这次,为了简洁,我们将告诉 tree 使用 -I 选项省略 node_modules 目录:

tree -I node_modules

随着你所做的添加,你的项目结构现在看起来像这样:

输出├── Dockerfile
├── README.md
├── app.js
├── controllers
│   └── sharks.js
├── db.js
├── models
│   └── sharks.js
├── package-lock.json
├── package.json
└── views
    ├── css
    │   └── styles.css
    ├── index.html
    └── sharks.html

现在你已经有了一个控制器组件来指导用户输入如何被保存和返回给用户,你可以进入创建视图的步骤,这些视图将实现你的控制器逻辑。

第五步 — 使用 EJS 和 Express 中间件收集和渲染数据

为了使我们的应用能够处理用户数据,我们将做两件事:首先,我们将包括一个内置的 Express 中间件函数 urlencoded(),它将使我们的应用能够解析用户输入的数据。其次,我们将向我们的视图中添加模板标签,以使我们的代码能够与用户数据动态交互。

要使用 Express 的 urlencoded() 函数,首先打开你的 app.js 文件:

nano app.js

在你的 express.static() 函数上方,添加以下行:

~/node_project/app.js

...
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));
...

添加这个函数将使你的应用能够访问解析后的 POST 数据。我们指定 trueextended 选项一起使用,以使我们的应用能够解析更灵活的数据类型(包括嵌套对象)。有关选项的更多信息,请参见函数文档。

保存并关闭文件,完成编辑。

接下来,我们将向视图中添加模板功能。首先,使用 npm install 安装 ejs 包:

npm install ejs

接下来,打开 views 文件夹中的 sharks.html 文件:

nano views/sharks.html

在第三步中,我们查看了这个页面,以确定如何编写我们的 Mongoose 模式和模型:

现在,我们将引入一个第三列,其中包含用户鲨鱼数据的 POST 请求的命名端点和 EJS 模板标签,这些标签将捕获该数据。这个列将放在前一个列的闭合 </p></div> 标签下方,以及行结束、容器和 HTML 文档的闭合标签上方。这些闭合标签已经在你的代码中就位;它们也在下面用注释标记。在你添加以下代码以创建新列时,请保留它们:

~/node_project/views/sharks.html

...
       </p> <!-- 前一列的 p 结束 -->
   </div> <!-- 前一列的 div 结束 -->
<div class="col-lg-4">
            <p>
                <form action="/sharks/addshark" method="post">
                    <div class="caption">输入你的鲨鱼</div>
                    <input type="text" placeholder="鲨鱼名称" name="name" <%= sharks[i].name; %>
                    <input type="text" placeholder="鲨鱼特征" name="character" <%= sharks[i].character; %>
                    <button type="submit">提交</button>
                </form>
            </p>
        </div>
    </div> <!-- 结束 row div -->
</div> <!-- 结束 container div -->

</html> <!-- 结束 html 标签 -->

form 标签中,你添加了一个 "/sharks/addshark" 端点用于用户鲨鱼数据,并指定 POST 方法来提交它。在输入字段中,你指定了 "鲨鱼名称""鲨鱼特征" 的字段,与之前定义的 Shark 模型保持一致。

为了将用户输入添加到你的 sharks 集合中,你使用了 EJS 模板标签(<%=, %>)以及 JavaScript 语法将用户的条目映射到新创建文档的适当字段。有关 JavaScript 对象的更多信息,请参见关于理解 JavaScript 对象的文章。有关 EJS 模板标签的更多信息,请参见 EJS 文档。

整个容器带有所有三列,包括带有鲨鱼输入表单的列,完成后将如下所示:

~/node_project/views/sharks.html

...
<div class="container">
    <div class="row">
        <div class="col-lg-4">
            <p>
                <div class="caption">一些鲨鱼对人类是危险的,尽管更多的鲨鱼并不构成威胁。例如,锯鲨并不被认为是对人类的威胁。</div>
                <img src="<https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg>" alt="锯鲨">
            </p>
        </div>
        <div class="col-lg-4">
            <p>
                <div class="caption">其他鲨鱼已知对人类友好和热情!</div>
                <img src="<https://assets.digitalocean.com/articles/docker_node_image/sammy.png>" alt="鲨鱼 Sammy">
            </p>
        </div>
	<div class="col-lg-4">
            <p>
                <form action="/sharks/addshark" method="post">
                    <div class="caption">输入你的鲨鱼</div>
                    <input type="text" placeholder="鲨鱼名称" name="name">
                    <input type="text" placeholder="鲨鱼特征" name="character">
                    <button type="submit">提交</button>
                </form>
            </p>
        </div>
    </div>
  </div>

</html>

保存并关闭文件,完成编辑。

现在你已经有一个收集用户输入的方式,你可以创建一个端点来展示返回的鲨鱼及其相关的特征信息。

复制新修改的 sharks.html 文件到一个名为 getshark.html 的文件:

cp views/sharks.html views/getshark.html

打开 getshark.html

nano views/getshark.html

在文件中,我们将修改我们用来创建鲨鱼输入表单的列,用一个展示我们 sharks 集合的列替换它。再次,你的代码将放在前一个列的 </p></div> 标签之间,以及行结束、容器和 HTML 文档的闭合标签之间。记得保留这些标签,当你添加以下代码以创建列时:

~/node_project/views/getshark.html

...
       </p> <!-- 前一列的 p 结束 -->
   </div> <!-- 前一列的 div 结束 -->
<div class="col-lg-4">
           <p>
              <div class="caption">你的鲨鱼</div>
                  <ul>
                     <% sharks.forEach(function(shark) { %>
                        <p>名称:<%= shark.name %></p>
                        <p>特征:<%= shark.character %></p>
                     <% }); %>
                  </ul>
            </p>
        </div>
    </div> <!-- 结束 row div -->
</div> <!-- 结束 container div -->

</html> <!-- 结束 html 标签 -->

这里你使用了 EJS 模板标签和 forEach() 方法来输出 sharks 集合中的每个值,包括最近添加的鲨鱼信息。

整个容器带有所有三列,包括带有你 sharks 集合的列,完成后将如下所示:

~/node_project/views/getshark.html

...
<div class="container">
    <div class="row">
        <div class="col-lg-4">
            <p>
                <div class="caption">一些鲨鱼对人类是危险的,尽管更多的鲨鱼并不构成威胁。例如,锯鲨并不被认为是对人类的威胁。</div>
                <img src="<https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg>" alt="锯鲨">
            </p>
        </div>
        <div class="col-lg-4">
            <p>
                <div class="caption">其他鲨鱼已知对人类友好和热情!</div>
                <img src="<https://assets.digitalocean.com/articles/docker_node_image/sammy.png>" alt="鲨鱼 Sammy">
            </p>
        </div>
	<div class="col-lg-4">
            <p>
              <div class="caption">你的鲨鱼</div>
                  <ul>
                     <% sharks.forEach(function(shark) { %>
                        <p>名称:<%= shark.name %></p>
                        <p>特征:<%= shark.character %></p>
                     <% }); %>
                  </ul>
            </p>
        </div>
    </div>
  </div>

</html>

保存并关闭文件,完成编辑。

为了使应用能够使用你创建的模板,你需要在 app.js 文件中添加几行。再次打开它:

nano app.js

在你的 db 常量上方,添加以下行:

~/node_project/app.js

...
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));

...

app.engine 方法告诉应用将 EJS 模板引擎映射到 HTML 文件,而 app.set 定义了默认的视图引擎。

你的 app.js 文件现在应该如下所示:

~/node_project/app.js

const express = require('express');
const app = express();
const router = express.Router();
const db = require('./db');

const path = __dirname + '/views/';
const port = 8080;

router.use(function (req,res,next) {
  console.log('/' + req.method);
  next();
});

router.get('/',function(req,res){
  res.sendFile(path + 'index.html');
});

router.get('/sharks',function(req,res){
  res.sendFile(path + 'sharks.html');
});

app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));
app.use('/', router);

app.listen(port, function () {
  console.log('示例应用正在 8080 端口监听!');
})

保存并关闭文件,完成编辑。

现在你已经创建了可以动态处理用户数据

的视图,是时候创建你的项目路由,将你的视图和控制器逻辑结合起来。

第六步 — 创建路由

将应用组件整合的最后一步是创建路由。我们将按功能分离我们的路由,包括一个路由到我们应用的首页,另一个路由到我们的鲨鱼页面。我们的 sharks 路由将是我们集成控制器逻辑与前一步中创建的视图的地方。

首先,创建一个 routes 目录:

mkdir routes

接下来,在这个目录中打开一个名为 index.js 的文件:

nano routes/index.js

这个文件首先导入 expressrouterpath 对象,允许我们使用 router 对象定义我们想要导出的路由,并使我们能够灵活地处理文件路径。在文件顶部添加以下代码:

~/node_project/routes/index.js

const express = require('express');
const router = express.Router();
const path = require('path');

接下来,添加以下 router.use 函数,它加载一个中间件函数,该函数将记录路由器的请求并将它们传递给应用的路由:

~/node_project/routes/index.js

...

router.use (function (req,res,next) {
  console.log('/' + req.method);
  next();
});

对应用根目录的请求首先会到这里,然后用户将被引导到我们接下来定义的应用首页。

最后,为了使这些路由可以作为可导入模块在应用的其他部分使用,在文件末尾添加一个闭包表达式来导出 router 对象:

~/node_project/routes/index.js

...

module.exports = router;

完成的文件如下所示:

~/node_project/routes/index.js

const express = require('express');
const router = express.Router();
const path = require('path');

router.use (function (req,res,next) {
  console.log('/' + req.method);
  next();
});

router.get('/',function(req,res){
  res.sendFile(path.resolve('views/index.html'));
});

module.exports = router;

保存并关闭此文件,完成编辑。

接下来,打开一个名为 sharks.js 的文件,定义应用应如何使用不同的端点和视图来处理用户鲨鱼输入:

nano routes/sharks.js

在文件顶部,导入 expressrouter 对象:

~/node_project/routes/sharks.js

const express = require('express');
const router = express.Router();

接下来,导入一个名为 shark 的模块,让你能够使用在控制器中定义的导出函数:

~/node_project/routes/sharks.js

const express = require('express');
const router = express.Router();
const shark = require('../controllers/sharks');

现在你可以使用 indexcreatelist 函数创建路由。每个路由将与相应的 HTTP 方法关联:在呈现主鲨鱼信息页面和返回用户鲨鱼列表给用户的情况下使用 GET,在创建新的鲨鱼条目的情况下使用 POST:

~/node_project/routes/sharks.js

...

router.get('/', function(req, res){
    shark.index(req,res);
});

router.post('/addshark', function(req, res) {
    shark.create(req,res);
});

router.get('/getshark', function(req, res) {
    shark.list(req,res);
});

每个路由都利用了 controllers/sharks.js 中定义的相关函数,因为我们已经在本文件顶部导入了该模块,使其可以被使用。

最后,关闭文件,并将这些路由附加到 router 对象上,然后导出它们:

~/node_project/routes/index.js

...

module.exports = router;

完成的文件如下所示:

~/node_project/routes/sharks.js

const express = require('express');
const router = express.Router();
const shark = require('../controllers/sharks');

router.get('/', function(req, res){
    shark.index(req,res);
});

router.post('/addshark', function(req, res) {
    shark.create(req,res);
});

router.get('/getshark', function(req, res) {
    shark.list(req,res);
});

module.exports = router;

保存并关闭文件,完成编辑。

使这些路由能够被你的应用访问的最后步骤是将它们添加到 app.js 中。再次打开该文件:

nano app.js

在你的 db 常量下方,添加以下导入你的路由:

~/node_project/app.js

...
const db = require('./db');
const sharks = require('./routes/sharks');

接下来,替换当前挂载你的 router 对象的 app.use 函数,用以下行,它将挂载 sharks 路由器模块:

~/node_project/app.js

...
app.use(express.static(path));
app.use('/sharks', sharks);

app.listen(port, function () {
        console.log("示例应用正在 8080 端口监听!");
})

你现在可以删除此文件中先前定义的路由,因为你是使用 sharks 路由器模块导入你的应用路由的。

你的 app.js 文件最终版本将如下所示:

~/node_project/app.js

const express = require('express');
const app = express();
const router = express.Router();
const db = require('./db');
const sharks = require('./routes/sharks');

const path = __dirname + '/views/';
const port = 8080;

app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path));
app.use('/sharks', sharks);

app.listen(port, function () {
  console.log('示例应用正在 8080 端口监听!');
})

保存并关闭文件,完成编辑。

你可以再次运行 tree 查看项目的最终结构:

tree -I node_modules

你的项目结构现在看起来像这样:

输出├── Dockerfile
├── README.md
├── app.js
├── controllers
│   └── sharks.js
├── db.js
├── models
│   └── sharks.js
├── package-lock.json
├── package.json
├── routes
│   ├── index.js
│   └── sharks.js
└── views
    ├── css
    │   └── styles.css
    ├── getshark.html
    ├── index.html
    └── sharks.html

现在你已经创建了并放置了所有应用组件,你已经准备好向数据库中添加一个测试鲨鱼了!

如果你按照前提条件中的初始服务器设置教程操作,你将需要修改你的防火墙,因为它目前只允许 SSH 流量。为了允许流量通过 8080 端口运行:

sudo ufw allow 8080

启动应用:

node app.js

接下来,导航到浏览器中的 http://your_server_ip:8080。你将看到以下登陆页面:

点击 获取鲨鱼信息 按钮。你将看到以下信息页面,其中添加了鲨鱼输入表单:

在表单中,选择一个鲨鱼。为了本演示的目的,我们将在 鲨鱼名称 字段中添加 Megalodon Shark,在 鲨鱼特征 字段中添加 Ancient

点击 提交 按钮。你将看到一个页面,显示这个鲨鱼的信息:

你还会在控制台中看到输出,表明鲨鱼已经被添加到你的集合中:

Output
Example app listening on port 8080!
{ name: 'Megalodon Shark', character: 'Ancient' }

如果你想创建一个新的鲨鱼条目,回到 鲨鱼 页面并重复添加鲨鱼的过程。

现在你已经拥有了一个工作正常的鲨鱼信息应用,允许用户添加他们最喜欢的鲨鱼信息。

结论

在本教程中,你通过集成 MongoDB 数据库并使用 MVC 架构模式重写应用逻辑,构建了一个 Node 应用。这个应用可以作为一个完整的 CRUD 应用的良好起点。