在 ejs 模板中显示数据

J C*_*ark 0 javascript ejs mongodb node.js express

我正在尝试创建一个简单的网页,提示用户输入时间并按提交。提交时,我希望与该集合对应的数据显示在网页上。

当我点击提交按钮时。它节省了我插入到相应 Mongo 集合中的时间。事实上,我什至使用 console.logging 来显示整个集合。我只是无法让它显示在网页上。

我是 NodeJS 和 MongoDB 的新手,请耐心等待。

这是 index.ejs 文件。Clients 是保存时间的集合名称。

<div>

    <ul class="clients">
        <% for(var i=0; i< clients.length; i++) {%>
            <li class="client">
                <span><%= clients[i].time %></span>

            </li>
        <% } %>
    </ul>
</div>
</head>

<body>

<form action="/clients" method="POST">
    <input type="text" placeholder="time" name="time">
    <button type="submit">Submit</button>
</form>
Run Code Online (Sandbox Code Playgroud)

我在我的 app.js 中有这个,它成功地将插入的时间发布到集合中。

app.post('/clients', (req, res) => {
var url = "mongodb://localhost:27017/lesson-data";
mongoose.connect(url, function (err, db) {
    if (err) throw err;
    db.collection('clients').save(req.body, (err, result) => {
        if (err) return console.log(err)

        console.log('saved to database')
        res.redirect('/')
    });
  });

});
Run Code Online (Sandbox Code Playgroud)

这在我的路线 > index.js - 它成功地将时间记录到控制台中,但不会显示在网页上。

router.get('/', function(req, res, next) {

var url = "mongodb://localhost:27017/lesson-data";
mongoose.connect(url, function (err, db) {
    if (err) throw err;

    db.collection('clients').find().toArray((err, result) => {
        if (err) return console.log(err);
        console.log(result );
        console.log("chea");
        // renders index.ejs
        res.render('index', {clients: result});
    });
  });
});
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?我觉得我已经很接近了,并且已经花了几个小时来试图解决这个问题。

Nei*_*unn 8

因此,这里不仅仅是一些错误,最好从头开始将其编写为一个小应用程序来解释事情。

创建并安装依赖项

您要做的第一件事是选择一个文件夹并为项目创建一个空间。您将需要项目中的一些子文件夹,以便您可以通过以下方式执行以下操作bash

 mkdir -p ejsdemo/{models,routes,views/pages}
Run Code Online (Sandbox Code Playgroud)

如果您在 Windows 上执行此操作,则可以执行任何您想要创建类似结构的操作,但您基本上希望在该顶级ejs-demo文件夹中出现这样的内容:

.
??? models
??? routes
??? views
    ??? pages
Run Code Online (Sandbox Code Playgroud)

然后你要初始化 nodejs 项目并安装依赖项。您可以使用以下方法再次执行此操作:

cd ejs-demo
npm init -y && npm i -S express ejs mongoose morgan body-parser
Run Code Online (Sandbox Code Playgroud)

同样,这可能会因您使用的操作系统而异,但您想要的是安装node_modulesejs-demo文件夹中的文件和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"
  }
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以package.json根据文件夹中的内容创建并运行npm i它,这基本上是它的缩写npm install,它将安装所有内容。

添加模型

models应该已经创建的子文件夹中,您现在要添加一个基本列表。Mongoose ODM(对象文档映射器)实际上有一个为您的集合注册“模型”的概念,这些模型定义了一个“模式”,并且还可以强制其他验证约束,甚至是特殊的实例方法或特殊目的的“静态”类方法。

将这些视为您收藏的“包装器”,实际上包括许多常见操作的助手并减少样板。我们只是在这里使用一个非常简单的模型进行演示,我们将其命名为:

模型/client.js

const { Schema } = mongoose = require('mongoose');

const clientSchema = new Schema({
  name: String,
  time: String
});

module.exports = mongoose.model('Client', clientSchema);
Run Code Online (Sandbox Code Playgroud)

这是非常基本的,并且只是导入Schema用于定义“模式”的辅助函数,该“模式”与mongoose.model()实际注册模型的函数一起使用。

这就是这个“模块”所需的全部内容,我们将require()在其他模块中使用该模型的相同文件。请注意,我们不需要知道这里的连接。

添加路线

通常,您希望从主应用程序逻辑中抽象出路由处理程序,并且有一种简单的方法可以做到这一点。按照您的示例,我们将在模块中创建两个路由,我们将再次require()在适当的位置:

路线/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;
Run Code Online (Sandbox Code Playgroud)

路线/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;
Run Code Online (Sandbox Code Playgroud)

这两个都是非常简单的例子。请注意它们是如何Client从之前创建的模型中导入的。两者也有一种方法分别是 GET 和 POST 并尝试使用“根”路径。这将是到稍后将注册的最终端点的相对路由。但是这样的结构允许添加“子路由”和其他要定义的 Http“动词”动作。

我正在使用async/awaitNodeJS 8.x 及更高版本来演示这些。如果您正在学习,那么这应该是您正在运行的最小版本。如果适合您的风格,您可以选择使用回调或普通承诺,但现代async/await语法通常会导致更清晰、更易于阅读的代码,您的同行将感谢您。

在任何一种情况下都非常简单地调用模型.find().create()从模型调用,它们只是“等待”使用,await因为它们每个都返回一个 Promise,您可以这样做。注意async每个函数处理程序的定义之前。这是为了纪念块作为async之前,你可以await对一个结果。

.find()课程是简单地返回集合中的所有数据,并且由于它是模型上的猫鼬方法,它已经返回作为Array提供方便。此外,.create()基本上是 的包装器insertOne(),它可以选择性地遍历要创建的文档数组,并且本质上是“保存”到集合中。这只是采用req.body,到实际调用此路由时,它将包含一个带有一些“已发布”表单内容的 JavaScript 对象。

添加视图

您还需要设置视图模板。这可能会再次涉及,但为了进行简单的演示,我们将只使用一个类似于问题中的基本模板:

视图/页面/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>
Run Code Online (Sandbox Code Playgroud)

我什至不关心样式或任何其他包装 HTML 结构。一个简单的列表和一个表单足以进行演示。还要注意现代for..of循环,它比通过索引引用数组元素要干净得多。EJS 基本上支持模板内的 JavaScript。因此,如果它是有效的 JavaScript,那么它对于模板使用也是有效的。在合理范围内:

主要应用

基本上剩下的就是index.js放在项目根文件夹中的主文件。实际上,我们在这里要做的就是加载我们之前创建的一些模块,注册端点,设置数据库连接并启动 http 侦听器。它主要是顺序的,但我们可以完成一些事情:

索引.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)
  }


})()
Run Code Online (Sandbox Code Playgroud)

列表的顶部只是我们之前在初始化项目时安装的主要模块中需要的一个块。这mongoose当然包括因为我们想要connect()MongoDB 以及express因为我们需要设置应用程序的主要处理程序。其他的东西morgan只是为了在确认请求的控制台中显示一些“日志”,bodyParser这非常重要,因为我们稍后需要解码来自表单的 POST 请求。

下一部分:

const Client = require('./models/client');
const rootRoutes = require('./routes/root');
const clientRoutes = require('./routes/clients');
Run Code Online (Sandbox Code Playgroud)

这只是导入我们之前创建的“模块”。通常,您不希望Client在此类index.js列表中使用或其他模型,但在本演示中,我们将为第一个请求设置一些数据。其他人导入我们之前设置的路由处理程序。

清单的下一部分实际上只是为 mongoose 设置,并且大部分是可选的。这里唯一真正重要的是用于实际连接的uriopts设置。这些就在示例列表的顶部附近,以防uriMongoDB 连接的需求发生变化。请注意,该演示是“自包含”的,因此请勿将其指向任何现有数据库,因为它需要一个未使用的名称。

然后是快速设置:

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);
Run Code Online (Sandbox Code Playgroud)

第一行为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)
  }


})()
Run Code Online (Sandbox Code Playgroud)

请注意,该块已标记,async因此我们可以await在其中使用关键字。还有try..catch用于错误处理的相同块样式。这里面的第一个简单调用实际上连接到了 MongoDB。这是正在运行的应用程序中第一个实际的异步方法调用。因此await,在我们进一步执行代码之前,请先了解它。它只是采用前面定义的uriopts参数。

由于这是一个“自包含”演示,我只是在我们做任何其他事情之前清空所有注册模型中的目标集合。不是您通常会做的那种事情,但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
Run Code Online (Sandbox Code Playgroud)

如果是这样并保持与上面提供的完全相同的代码,那么它就可以运行了:

node index.js
Run Code Online (Sandbox Code Playgroud)

然后您应该会看到这些行出现:

Mongoose: clients.deleteMany({}, {})
Mongoose: clients.insertMany([ { _id: 5ca06fbc38a9b536315d732c, name: 'One', time: '2:00', __v: 0 }, { _id: 5ca06fbc38a9b536315d732d, name: 'Two', time: '3:00', __v: 0 } ], {})
Run Code Online (Sandbox Code Playgroud)

现在您应该准备好打开浏览器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"
Run Code Online (Sandbox Code Playgroud)

当然,这也显示了从 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"
Run Code Online (Sandbox Code Playgroud)

其示出了解析的 req.body内容,并将所得insertOne()create()该模型的方法,以及当然记录的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"
Run Code Online (Sandbox Code Playgroud)

结论

这些是您需要在自己的应用程序中重复使用的基本概念。我们在这里介绍的基本内容是:

  • 创建模型- 在其中为每个集合定义模型,允许您设置模式规则。Mongoose 可以选择设置为{ strict: false }并且根本不调用任何模式验证或类型转换。它通常比处理核心驱动程序方法更友好一些。

  • 单独的路由- 操作和处理程序可以在逻辑组中设置它们需要的位置,而无需绑定到严格的端点。最终端点的设置可以稍后完成,这个“控制器”接口实际上只是表示视图和模型之间的“握手层”。

  • 一次连接到数据库- 这是一个重要的规则,Mongoose 的一般使用模式有助于强制执行。您的基于请求的应用程序在每个请求中都没有连接和断开连接的业务(正如您所做的那样)。您只连接一次并保持打开状态。驱动程序实际上将管理诸如连接池之类的事情并帮助分发,因此不会阻止多个并发请求。

    此外,池中的任何其他连接都将由驱动程序管理,并在不需要时断开连接。尽管通常有一个默认池大小保持打开状态,但始终为下一个请求做好准备。一般来说,在这个阶段你不应该担心这些,因为这是一个细节,只有当你真正遇到需要知道的时候才需要了解。这不会是一段时间。

基本上,如果您遵循这里的所有内容,那么您就有了一个工作示例,说明您基本上尝试做的事情以及您可以“构建”以做得更大更好的事情。

玩得开心!