因此,这里不仅存在一些问题,最好从头开始将其编写为一个小应用程序来解释问题。
创建和安装依赖项
您要做的第一件事是选择一个文件夹并为项目创建一个空间。您将需要项目中的一些子文件夹,以便您可以通过bash 执行此类操作(如果有):
mkdir -p ejsdemo/{models,routes,views/pages}
如果您在 Windows 上执行此操作,则可以执行任何您想要创建类似结构的操作,但您基本上希望在顶级 ejs-demo 文件夹中出现类似这样的内容:
.
├── models
├── routes
└── views
└── pages
然后你想初始化 nodejs 项目并安装依赖项。您可以使用以下方法再次执行此操作:
cd ejs-demo
npm init -y && npm i -S express ejs mongoose morgan body-parser
同样,这可能会因您使用的操作系统而异,但您需要的是在 ejs-demo 文件夹中安装的 node_modules 和一个 package.json 文件,该文件基本上读作:
{
"name": "ejsdemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.18.3",
"ejs": "^2.6.1",
"express": "^4.16.4",
"mongoose": "^5.4.20",
"morgan": "^1.9.1"
}
}
或者,您可以根据文件夹中的内容创建package.json,然后运行npm i,它基本上是npm install 的缩写,它将安装所有内容。
添加模型
在应该已经创建的models 子文件夹中,您现在可以添加基本列表。 Mongoose ODM(对象文档映射器)实际上具有为您的集合注册“模型”的概念,这些模型定义了“模式”,并且还可以强制执行其他验证约束,甚至是特殊实例方法或用于特殊目的的“静态”类方法。
将这些视为您的集合的“包装器”,实际上包括许多常见操作的帮助器并减少样板文件。我们在这里只是使用一个非常简单的模型进行演示,我们将其命名为:
models/client.js
const { Schema } = mongoose = require('mongoose');
const clientSchema = new Schema({
name: String,
time: String
});
module.exports = mongoose.model('Client', clientSchema);
这是非常基本的,只需导入Schema 帮助函数来定义一个“模式”,该模式与实际注册模型的mongoose.model() 函数一起使用。
这就是这个“模块”中需要做的所有事情,我们将require()这个相同的文件放在我们想要使用这个模型的其他模块中。请注意,我们不需要了解此处的连接。
添加路线
通常,您希望从主应用程序逻辑中抽象出路由处理程序,并且有一种简单的方法可以做到这一点。按照您的示例,我们将在模块中创建两条路由,我们将在适当的位置再次require():
routes/root.js
const express = require('express');
const router = express.Router();
const Client = require('../models/client');
router.get('/', async (req, res, next) => {
try {
let clients = await Client.find();
console.log(clients);
res.render('pages/index', { clients });
} catch (e) {
next(e);
}
});
module.exports = router;
routes/clients.js
const express = require('express');
const router = express.Router();
const Client = require('../models/client');
router.post('/', async (req, res, next) => {
try {
console.log(req.body);
await Client.create(req.body);
res.redirect('/');
} catch (e) {
next(e);
}
});
module.exports = router;
这两个都是非常简单的例子。请注意它们是如何从之前创建的模型中导入 Client 的。两者也有一种方法分别是 GET 和 POST,并尝试使用“根”路径。这将是到稍后将注册的最终端点的相对路由。但是这样的结构允许添加“子路由”和其他要定义的 Http“动词”动作。
我正在使用 NodeJS 8.x 及更高版本中的 async/await 来演示所有这些。如果您正在学习,那么这应该是您正在运行的最低版本。如果适合您的风格,您可以选择使用回调或简单的 Promise,但现代的 async/await 语法通常会导致代码更简洁、更易于阅读,您的同行会感谢您。
在任何一种情况下,模型对.find() 或.create() 的调用都非常简单,使用await 只是“等待”,因为它们每个都返回一个Promise,您可以这样做。注意每个函数处理程序定义之前的async。需要先将块标记为 async,然后才能在结果上使用 await。
.find() 当然只是简单地返回集合中的所有数据,因为它是模型上的猫鼬方法,为了方便起见,它已经作为Array 返回。此外,.create() 基本上是insertOne() 的包装器,它可以选择性地遍历要创建的文档数组,并且基本上“保存”到集合中。这只是采用req.body,在实际调用此路由时,它将包含一个带有一些“已发布”表单内容的 JavaScript 对象。
添加视图
您还需要设置视图模板。同样,这可能会涉及到,但为了一个简单的演示,我们将只使用一个类似于问题中的基本模板:
views/pages/index.ejs
<div>
<ul class="clients">
<% for ( let client of clients ) { %>
<li class="client">
<span><%= client.name %>
<span><%= client.time %>
</li>
<% } %>
</ul>
</div>
<form action="/clients" method="POST">
<input type="text" placeholder="name" name="name">
<input type="text" placeholder="time" name="time">
<div>
<button type="submit">Submit</button>
</div>
</form>
我什至不关心样式或任何其他包装 HTML 结构。一个简单的列表和一个表格就足以进行演示了。还要注意现代的for..of 循环,它比通过索引引用数组元素要干净得多。 EJS 基本上支持模板中的 JavaScript。因此,如果它是有效的 JavaScript,那么它对于模板使用是有效的。在合理范围内:
主要应用
基本上剩下的就是放在项目根文件夹中的主要index.js 文件。实际上,我们在这里要做的就是加载我们之前创建的一些模块,注册端点设置数据库连接并启动 http 侦听器。它主要是顺序的,但我们可以运行一些事情:
index.js
const mongoose = require('mongoose');
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const Client = require('./models/client');
const rootRoutes = require('./routes/root');
const clientRoutes = require('./routes/clients');
const uri = 'mongodb://localhost:27017/lesson-test';
const opts = { useNewUrlParser: true };
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);
mongoose.set('debug', true);
const app = express();
app.set('view engine', 'ejs');
app.use(morgan('combined'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/', rootRoutes);
app.use('/clients', clientRoutes);
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// Clean data for demo
await Promise.all(
Object.entries(conn.models).map(([k, m]) => m.deleteMany())
);
// Insert some starter sample
await Client.insertMany([
{ name: 'One', time: '2:00' },
{ name: 'Two', time: '3:00' }
]);
app.listen(3000);
} catch (e) {
console.error(e)
}
})()
清单顶部只是我们之前在初始化项目时安装的主要模块中需要的一个块。这当然包括mongoose,因为我们想要connect() 到MongoDB 和express,因为我们需要设置应用程序的主要处理程序。 morgan 之类的其他内容只是为了在控制台中显示一些“日志记录”以确认请求,bodyParser 这非常重要,因为我们需要稍后从表单中解码 POST 请求。
下一部分:
const Client = require('./models/client');
const rootRoutes = require('./routes/root');
const clientRoutes = require('./routes/clients');
这只是导入我们之前创建的“模块”。通常你不会想要Client 或这种index.js 列表中的其他模型,但是对于这个演示,我们将为第一个请求设置一些数据。其他的导入我们之前设置的路由处理程序。
清单的下一部分实际上只是为 mongoose 设置,并且大部分是可选的。这里唯一重要的是uri 和opts 设置,它们用于实际连接。这些只是在示例列表的顶部附近,以防uri 需要更改您的 MongoDB 连接。请注意,该演示是“自包含的”,因此请勿将其指向任何现有数据库,因为它需要一个未使用的名称。
然后是快速设置:
app.set('view engine', 'ejs');
app.use(morgan('combined'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/', rootRoutes);
app.use('/clients', clientRoutes);
第一行为没有其他设置的模板注册ejs,因此在我们已经定义的地方使用默认位置。 morgan 行设置请求记录中间件,就像两个 bodyParser 调用也注册相应的中间件以进行 JSON 解析和 UrlEndcoded 内容,其中后者是 HTML 表单帖子的默认设置。
最后两行将这些导入用于路由处理程序并将它们分配给最终端点。这就是为什么在定义本身中,两个请求处理程序都使用/,因为这与app.use() 下定义的端点相关。这是很常见的做法。
接下来是主要的代码块,同样非常简单:
(async function() {
try {
const conn = await mongoose.connect(uri, opts);
// Clean data for demo
await Promise.all(
Object.entries(conn.models).map(([k, m]) => m.deleteMany())
);
// Insert some starter sample
await Client.insertMany([
{ name: 'One', time: '2:00' },
{ name: 'Two', time: '3:00' }
]);
app.listen(3000);
} catch (e) {
console.error(e)
}
})()
请注意,该块标记为async,因此我们可以在其中使用await 关键字。还有相同的try..catch 块样式用于错误处理。其中的简单第一个调用实际上连接到 MongoDB。这是正在运行的应用程序中的第一个实际异步方法调用。因此,在我们进一步执行代码之前,请您 await 它。它只是采用前面定义的uri 和opts 参数。
由于这是一个“自包含”的演示,我只是在我们做任何其他事情之前清空所有注册模型中的目标集合。不是你通常会做的那种事情,但Promise.all( Object.entries(..).map(..) ) 事情基本上是一种为每个注册模型处理猫鼬的方法。该“注册”发生在初始 require() 中,适用于列表顶部附近显示的任何型号。
接下来的事情应该很明显,因为我们只是使用Client.insertMany() 插入一些示例数据开始。同样,这是一个异步函数,因此您 await 继续执行之前的结果。
最后我们应该很高兴我们已经连接到 MongoDB 并且已经插入了一些示例数据作为开始,所以可以在端口 3000 或 localhost 作为默认值开始监听请求。
运行应用程序
如果您已经完成了所有这些,那么目录结构现在应该看起来像这样(当然省略了node_modules 下的所有细节):
.
├── index.js
├── models
│ └── client.js
|── node_modules
├── package.json
├── package-lock.json
├── routes
│ ├── clients.js
│ └── root.js
└── views
└── pages
└── index.ejs
如果是这样并保持与上面给出的完全相同的代码,那么它就可以运行了:
node index.js
然后您应该会看到这些行出现:
Mongoose: clients.deleteMany({}, {})
Mongoose: clients.insertMany([ { _id: 5ca06fbc38a9b536315d732c, name: 'One', time: '2:00', __v: 0 }, { _id: 5ca06fbc38a9b536315d732d, name: 'Two', time: '3:00', __v: 0 } ], {})
现在您应该准备好打开浏览器到http://localhost:3000/ 并查看分配给该路由的渲染模板。您运行应用程序的控制台应指示该路由已被命中:
Mongoose: clients.find({}, { projection: {} })
[ { _id: 5ca06fbc38a9b536315d732c,
name: 'One',
time: '2:00',
__v: 0 },
{ _id: 5ca06fbc38a9b536315d732d,
name: 'Two',
time: '3:00',
__v: 0 } ]
::ffff:10.0.2.2 - - [31/Mar/2019:07:45:26 +0000] "GET / HTTP/1.1" 200 423 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
当然,这也显示了从 Mongoose 到 MongoDB 服务器的请求。现在应该在页面上的 <li> 项中呈现相同的数据。
您也可以填写表单字段并提交,这应该会在控制台中显示如下响应:
{ name: 'Four', time: '4:00' }
Mongoose: clients.insertOne({ _id: ObjectId("5ca0710038a9b536315d732e"), name: 'Four', time: '4:00', __v: 0 })
::ffff:10.0.2.2 - - [31/Mar/2019:07:49:20 +0000] "POST /clients HTTP/1.1" 302 46 "http://localhost:3000/" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
其中显示了 解析 req.body 内容和从模型的 create() 方法生成的 insertOne(),当然还有 POST 请求的记录。然后重定向操作将返回到/ 路由:
Mongoose: clients.find({}, { projection: {} })
[ { _id: 5ca06fbc38a9b536315d732c,
name: 'One',
time: '2:00',
__v: 0 },
{ _id: 5ca06fbc38a9b536315d732d,
name: 'Two',
time: '3:00',
__v: 0 },
{ _id: 5ca0710038a9b536315d732e,
name: 'Four',
time: '4:00',
__v: 0 } ]
::ffff:10.0.2.2 - - [31/Mar/2019:07:49:20 +0000] "GET / HTTP/1.1" 200 504 "http://localhost:3000/" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
结论
这些是您需要在自己的应用程序中重复使用的基本概念。我们在这里介绍的基本内容是:
创建模型 - 您可以在其中为每个集合定义一个模型,从而为架构设置规则。 Mongoose 可以选择设置为{ strict: false },并且根本不调用任何模式验证或类型转换。它通常比处理核心驱动方法更友好一些。
单独的路由 - 可以将操作和处理程序设置在逻辑组中,以便在不绑定到严格端点的情况下将它们设置在需要的位置。最终端点的设置可以在以后完成,这个“控制器”接口实际上只是表示视图和模型之间的“握手层”。
-
一次连接到数据库 - 这是一个重要规则,并且被 Mongoose 的一般使用模式强制执行。您基于请求的应用程序在每个请求中没有业务连接和断开连接(就像您所做的那样)。您只能连接 ONCE 并保持打开状态。驱动程序实际上将管理诸如连接池之类的事情并帮助分发,因此多个并发请求不会被阻塞。
此外,池中的任何其他连接将由驱动程序管理,并在不需要时断开连接。尽管通常有一个默认的池大小保持打开状态,始终为下一个请求做好准备。一般来说,在这个阶段你不应该担心这些,因为这是一个细节,当你真正遇到需要知道的时候,你才需要了解这些细节。这不会持续一段时间。
基本上,如果您遵循此处的所有内容,那么您就有了一个工作示例,说明您基本上尝试做的事情以及您可以“建立”以做得更大更好的事情。
玩得开心!