最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Showing data in ejs template - Stack Overflow

programmeradmin4浏览0评论

I am trying to create a simple webpage that prompts the user to enter a time and press submit. On submit, I want the data that corresponds with that collection to be shown on the webpage.

When I click on the submit button. It saves the time I inserted, into the respective Mongo collection. In fact, I even have it console.logging to show the entire collection. I just can not get it to show on the webpage.

I am new to NodeJS and MongoDB, so bear with me.

Here is the index.ejs file. Clients is the collection name that holds times.

<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>

I have this in my app.js, which posts the inserted time into the collection successfully.

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('/')
    });
  });

});

And this in my routes > index.js - which successfully logs the times into the console, but wont show on the webpage .

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});
    });
  });
});

What am I doing wrong? I feel like I am close and have put multiple hours into attempting to solve this.

I am trying to create a simple webpage that prompts the user to enter a time and press submit. On submit, I want the data that corresponds with that collection to be shown on the webpage.

When I click on the submit button. It saves the time I inserted, into the respective Mongo collection. In fact, I even have it console.logging to show the entire collection. I just can not get it to show on the webpage.

I am new to NodeJS and MongoDB, so bear with me.

Here is the index.ejs file. Clients is the collection name that holds times.

<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>

I have this in my app.js, which posts the inserted time into the collection successfully.

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('/')
    });
  });

});

And this in my routes > index.js - which successfully logs the times into the console, but wont show on the webpage .

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});
    });
  });
});

What am I doing wrong? I feel like I am close and have put multiple hours into attempting to solve this.

Share Improve this question edited Mar 31, 2019 at 8:17 Neil Lunn 151k36 gold badges355 silver badges325 bronze badges asked Mar 30, 2019 at 18:20 Mr. MayonnaiseMr. Mayonnaise 1591 gold badge2 silver badges13 bronze badges 5
  • what does the page shows ? nothing ? you still see your form ? What response do you get in your network tab? Also, isn't your find method missing the parameters to find a specific result? – Gonzalo.- Commented Mar 30, 2019 at 18:23
  • Instead, the page redirects back to the index file (the page that it’s starting from). So, yes I still see my form – Mr. Mayonnaise Commented Mar 30, 2019 at 21:45
  • Well it’s finding the collection Client because it’s logging the results in the console successfully. If it’s logging correctly, shouldn’t it show in the <ul> correctly? – Mr. Mayonnaise Commented Mar 30, 2019 at 21:51
  • are you sure that clients and client html classes don't hide the ul? – dimitris tseggenes Commented Mar 30, 2019 at 22:14
  • can you post console.log(result) – 1565986223 Commented Mar 31, 2019 at 4:12
Add a ment  | 

1 Answer 1

Reset to default 9

So there are more than just a few things wrong here and it's probably best to step through writing this as a small application from scratch to explain things.

Create and install dependencies

First thing you want to do is pick a folder and create a space for the project. You are going to want a few sub-folders in the project so you can do something like this through bash if you have that:

 mkdir -p ejsdemo/{models,routes,views/pages}

If you're doing this on windows then do whatever you want to create a similar structure but you basically want something that es out like this within that top level ejs-demo folder:

.
├── models
├── routes
└── views
    └── pages

Then you want to intitialize the nodejs project and install dependencies. You can do this again with the following:

cd ejs-demo
npm init -y && npm i -S express ejs mongoose morgan body-parser

Again that might vary depending on which OS you are using, but what you want is an installed node_modules within the ejs-demo folder and a package.json file that basically reads as:

{
  "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"
  }
}

Optionally you could just create the package.json based on that content within the folder and run npm i which is basically short for npm install and that will install everything.

Add Models

Within the models sub-folder that should already be created, you now what to add a basic listing. The Mongoose ODM ( Object Document Mapper ) actually has a concept of registering "models" for your collections which define a "schema" and can also enforce other validation constraints or even special instance methods or "static" class methods for special purposes.

Think of these as "wrappers" for you collection, that actually include helpers for a lot of mon actions and reduce boilerplate. We are just using a very simple model for demonstration here that we will name as:

models/client.js

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

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

module.exports = mongoose.model('Client', clientSchema);

This is very basic, and simply imports the Schema helper function for defining a "schema" which is used with the mongoose.model() function that actually registers the model.

This is all that needs to go in this "module", and we will require() this same file in other modules where we want to use this model. Note that we don't need to know about a connection here.

Add Routes

Typically you would want to abstract the route handlers from the main application logic, and there is a simple way to do that. Following your example we will create two routes within modules which we will again require() in the appropriate place:

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;

Both of these are again very simple examples. Note how they both import the Client from the model created earlier. Both also have one method being GET and POST respectively and are tried to a "root" path. This will be a relative route to the final endpoint which will be registered later. But such a structure allows addition of "sub-routes" and other Http "verb" actions to be defined.

I'm demonstrating these all using async/await from NodeJS 8.x and greater. If you are learning then this should be the minimal version you are running on. You can optionally use callbacks or plain promises if it suits your style, but the modern async/await syntax will generally lead to cleaner and easier to read code that your peers will thank you for.

Very simple calls to either .find() or .create() from the model in either case, which are simply 'awaited' using await as they each return a Promise and you can do so. Note the async before the definition of each function handler. This is required to mark the block as async before you can await on a result.

The .find() of course is simply returning all data in the collection, and since it is a mongoose method on the model it returns already as an Array for convenience. Also the .create() is basically a wrapper of insertOne(), that can optionally iterate through an array of documents to create and that essentially "saves" to the collection. This is simply taking the req.body, which by the time this route is actually called will contain a JavaScript Object with some "posted" form content.

Add Views

Also you need to set up the view templates. Again this can get involved, but for a simple demonstration we will just use one basic template similar to the one in the question:

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>

I'm not even bothering with styling or any other wrapping HTML structure. A simple list and a Form are good enough for demonstration. Also note the modern for..of loop which is a lot cleaner than referring to array elements by index. EJS basically supports JavaScript within the templates. So if it's valid JavaScript then it's valid for template usage. Within reason:

Main Application

All that is basically left is the main index.js file to put in the root folder of the project. Really all we are going to do here is load in some of the modules we created earlier, register endpoints set up a database connection and start the http listener. It's mostly sequential but we can run through some things:

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('bined'));
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)
  }


})()

Right up the top of the listing is just a block to require in the main modules that we installed earlier with initializing the project. This includes mongoose of course since we want to connect() to MongoDB and the express since we need to set up the main handlers of the application. The other things like morgan are there just to show some "logging" in the console acknowledging the requests, and bodyParser which is very important since we nee to decode a POST request from a form later.

The next part:

const Client = require('./models/client');
const rootRoutes = require('./routes/root');
const clientRoutes = require('./routes/clients');

This is just importing the "modules" we created earlier. Normally you would not want the Client or other models in this sort of index.js listing, but for this demonstration we are going to set up some data ready for the first request. The others import the route handlers we set up earlier.

The next part of the listing is really just setup for mongoose and is mostly optional. The only real things of importance here are the uri and opts settings which are for the actual connection. These are just near the top of an example listing in case the uri needs changes for your MongoDB connection. Note the demonstration is "self-contained" so DO NOT point this at any existing database as it is expecting a name that is otherwise not in use.

Then there's the express set-up:

app.set('view engine', 'ejs');
app.use(morgan('bined'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.use('/', rootRoutes);
app.use('/clients', clientRoutes);

The first line registers ejs for the templates with no other settings, so the default locations are used in the places we already defined. The morgan line sets up the request logging middleware as do the two bodyParser invocations also registering the respective middleware for JSON parsing and UrlEndcoded content, where the latter is the default for HTML Form posts.

The last two lines take those imports for the route handlers and assign them to eventual endpoints. This is why in the definitions themselves both request handlers used / since that is relative to the endpoints defined here under app.use(). This is pretty mon practice.

Next is the main code block, which is again fairly straightforward:

(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)
  }


})()

Note the block is marked async so we can use the await keyword within it. Also there is the same try..catch block style for error handling. The simple first call within this actually connects to MongoDB. This is the first actual asynchronous method call in the running application. So you await it before we go any further in the code execution. It is just taking the uri and opts arguments defined earlier.

Since this is a "self contained" demonstration, I'm just emptying the target collections from all registered models before we do anything else. Not the sort of thing you would typically do but the Promise.all( Object.entries(..).map(..) ) thing is basically a way of processing something for every registered model with mongoose. That "registration" happens in the initial require() for any model as shown near the top of the listing.

The next thing should be pretty obvious as we are just using Client.insertMany() to insert some sample data to start with. Again this is an asynchronous function so you await the result before continued execution.

Finally we should be happy that we are connected to MongoDB and have inserted some sample data to start with, so it's okay to start listening for requests on port 3000 of localhost as the default.

Running the Application

If you have all of this in place then the directory structure should now look something like this ( omitting all details under node_modules of course ):

.
├── index.js
├── models
│   └── client.js
|── node_modules
├── package.json
├── package-lock.json
├── routes
│   ├── clients.js
│   └── root.js
└── views
    └── pages
        └── index.ejs

If it's like that and keeping the exact same code as presented above then it's ready to run with:

node index.js

You should then see these lines appear:

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

Now you should be ready to open your browser to http://localhost:3000/ and view the rendered template assigned to that route. The console where you ran the application should indicate that the route has been hit:

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"

And of course that also shows the request from Mongoose to the MongoDB server. The same data should now be rendered within the <li> items on the page.

You can also fill in the form fields and submit, which should show in the console a response like:

{ 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"

Which shows the parsed req.body content and the resulting insertOne() from the create() method of the model, and of course the record of the POST request. The redirect action will then lead back to the / route:

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"

Conclusion

And those are the basic concepts you need to go and repeat in your own applications. The basic things we covered here were:

  • Create Models - Where you define a model for each collection, allowing you to set up rules for schema. Mongoose can optionally be set to { strict: false } and not invoke any schema validation or type conversion at all. It's generally a bit more friendly than dealing with the core driver methods.

  • Separate Routes - Actions and handlers can be set up in logical groups for where they would need to be without being bound to a strict endpoint. Set-up of eventual enpoints can be done later, and this "controller" interface is really just the "handshake layer" between the presentation view and the models.

  • Connect to the Database ONCE - This is an important rule and is helpfully enforced by the general usage pattern of Mongoose. Your request based application has no business connecting and disconnecting within each request ( as you were doing ). You only ever connect ONCE and leave that open. The driver will actually manage things like connection pooling and help distribute so multiple concurrent requests are not blocked.

    Also any additional connections within the pool will be managed by the driver and disconnected when they are not needed. Though there is typically a default pool size left open always ready for the next request. Generally you should not worry about these at this stage as it's a detail you only need to learn about when you really e across the need to know. And that will not be for a while.

Basically if you follow everything here, then you have a working example of what you were basically trying to do and something you can "build upon" to make bigger and better.

Have fun!

发布评论

评论列表(0)

  1. 暂无评论