Fetch data with mongoose in ejs template file - node.js

I want to fetch all the users in my db inside my ejs file. I have 3 files one model for the data, one ejs file and one js file for my route. The problem is that I don't have any error displaying. I just have an empty list rendered on my browser as if there was no users in the database. Can someone help me?
Here is my route js file :
import express from 'express';
import User from "../models/user.js"
const routerUserList = express.Router();
routerUserList.get('/user-list', function(req, res, next) {
try {
const users = User.find({});
res.render('user-list', {
pageTitle: 'Liste des utilisateurs',
path: '/user-list',
users: users
})
} catch (error) {
res.status(500).json({message : "Une erreur est survenue"})
}
})
export default routerUserList
Here is my ejs file :
<%- include('./includes/head.ejs') %>
</head>
<body>
<%- include('./includes/navigation.ejs') %>
<main>
<h1>Liste des utilisateurs</h1>
<% for(var i=0; i < users.length; i++) {%>
<li><%= users[i]._id %></li>
<li><%= users[i].name %></li>
<li><%= users[i].email %></li>
<% } %>
</main>
<%- include('./includes/end.ejs') %>
Here is my model file :
import mongoose from 'mongoose'
const userSchema = mongoose.Schema({
name: {type: String, required: true},
email: {type: String, required: true},
password: {type: String, required: true},
id: {type: String}
})
export default mongoose.model("User", userSchema)

mongoose model always returns Promise, you should resolve this promise to get an actual array of users.
Updated code will look like this.
import express from 'express';
import User from "../models/user.js"
const routerUserList = express.Router();
routerUserList.get('/user-list', async function(req, res, next) {
try {
const users = await User.find({});
res.render('user-list', {
pageTitle: 'Liste des utilisateurs',
path: '/user-list',
users: users
})
} catch (error) {
res.status(500).json({message : "Une erreur est survenue"})
}
})
export default routerUserList

Related

Express.js application bug: validationResult(req) method does not work

I am working on a blogging application (click the link to see the GitHub repo) with Express, EJS and MongoDB.
Before submitting a new post, of course, I have to validate the form entries. I use express-validator version 6.3.0.
My addPost controller:
exports.addPost = (req, res, next) => {
// Form validation rules
check('title', 'The title field id required')
.not()
.isEmpty();
check('excerpt', 'The excerpt field id required')
.not()
.isEmpty();
check('body', 'The full text field id required')
.not()
.isEmpty();
const errors = validationResult(req);
if (!errors.isEmpty()) {
console.log(errors.array());
}
if (!errors.isEmpty()) {
res.render('admin/addpost', {
layout: 'admin/layout',
website_name: 'MEAN Blog',
page_heading: 'Dashboard',
page_subheading: 'Add New Post',
errors: errors
});
req.flash('danger', errors);
req.session.save(() => res.redirect('/dashboard'));
} else {
const post = new Post();
post.title = req.body.title;
post.short_description = req.body.excerpt
post.full_text = req.body.body;
post.save(function(err) {
if (err) {
console.log(err);
return;
} else {
req.flash('success', "The post was successfully added");
req.session.save(() => res.redirect('/dashboard'));
}
});
}
}
The Post model:
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
short_description: {
type: String,
required: true
},
full_text: {
type: String,
required: true
},
post_image: {
type: String,
required: false
},
updated_at: {
type: Date,
default: Date.now()
},
created_at: {
type: Date,
default: Date.now()
}
});
The error messages are nor rendered in the view, which looks like this:
<div id="messages" class="text-center">
<% Object.keys(messages).forEach(function (type) { %>
<% messages[type].forEach(function (message) { %>
<div class="alert alert-<%= type %>"><%= message %></div>
<% }) %>
<% }) %>
</div>
UPDATE:
The index.js file in the root has this code:
const express = require("express");
const dotenv = require("dotenv");
const mongoose = require("mongoose");
const path = require("path");
const morgan = require("morgan");
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const expressLayouts = require("express-ejs-layouts");
const flash = require("express-flash");
const session = require("express-session");
const app = express();
dotenv.config();
//Conect to MONGODB
mongoose
.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => {
console.log("conected");
});
mongoose.connection.on("error", err => {
console.log(`DB connection error: ${err.message}`);
});
// Set static directory
app.use(express.static(path.join(__dirname, "public")));
// Set views directory
app.set("views", path.join(__dirname, "views"));
// Set view engine
app.set("view engine", "ejs");
// Use Express Layouts
app.use(expressLayouts);
// Morgan Middleware
app.use(morgan("dev"));
// support parsing of application/json type post data
app.use(bodyParser.json());
//support parsing of application/x-www-form-urlencoded post data
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
// Express Sessions Middleware
app.use(session({
secret: '123',
resave: true,
saveUninitialized: true
}));
// Express Messages Middleware
app.use(flash());
app.use(function (req, res, next) {
res.locals.messages = require('express-messages')(req, res);
next();
});
// Bring the Dashboard
const dashboardRoute = require("./routes/admin/dashboard");
// Get Dashboard Routes
app.use('/dashboard', dashboardRoute);
What am I doing wrong?
exports.addPost = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
req.flash('errors', errors.array())
req.session.save(() => res.redirect('../addpost'));
//return res.status(400).send(errors.array());
} else {
const post = new Post();
post.title = req.body.title;
post.short_description = req.body.excerpt
post.full_text = req.body.body;
post.save(function(err){
if(err){
console.log(err);
return;
} else {
req.flash('success', "The post was successfully added");
req.session.save(() => res.redirect('/dashboard'));
}
});
}
}
messages.ejs
<div id="messages" class="text-center">
<% Object.keys(messages).forEach(function (type) { %>
<% messages[type].forEach(function (message) { %>
<% if (type === 'errors') {%>
<div class="alert alert-<%= type %>"><%= message.msg %></div>
<%} else { %>
<div class="alert alert-<%= type %>"><%= message %></div>
<% } %>
<% }) %>
<% }) %>
I guess this is what you intended to do
You are rendering a template then trying to show flash and then redirect again. Change it to this
req.flash('danger', errors);
req.session.save(() => res.redirect('/dashboard'));
Forget the render... It makes no sense for you to have it there. What render does, it renders and returns a template. Therefore your req.flash and redirect never happens or it happens after the header have already been sent.
res.render() definition:
Renders a view and sends the rendered HTML string to the client.
Optional parameters:
locals, an object whose properties define local variables for the
view. callback, a callback function. If provided, the method returns
both the possible error and rendered string, but does not perform an
automated response. When an error occurs, the method invokes next(err)
internally.
AND
exports.addPost = (req, res, next) => {
// Form validation rules
req.check('title').not().isEmpty().withMessage("The title field is mandatory");
req.check('body').not().isEmpty().withMessage("The full text field is mandatory");
const errors = req.validationErrors();
try change your if statement from this:
if (!errors.isEmpty()) {
console.log('there are no validation errors');
} else {
console.log(errors);
}
}
to this:
exports.addPost = (req, res, next) => {
// Form validation rules
check('title', '<your error message>')
.not()
.isEmpty();
check('excerpt', '<your error message>')
.not()
.isEmpty();
check('body', '<your error message>')
.not()
.isEmpty();
const errors = validationResult(req);
const errors = validationResult(req);
if (!errors.isEmpty()) {
console.log(errors.array());
}
}
Edit
If you would like to send an response to your front-end replace the console.log() command into res.send() then parse the answer in your front-end
like so:
if (!errors.isEmpty()) {
return res.send(errors.array());
// can also send an status and catch it
// by sending res.status(400).send(errors.array());
}
Hopefully this makes sense
from what i see in the documentation of express-validator you need to provide an array of validation rules(those checks at the top of your controller) when you define the route.
It doesn't make much sense for them to be at the top of the request handler since the express-validator won't be able to access the context that provides the request to be validated.
So in the router you need something like this:
router/front-end/posts.js
const validationRules = [// Form validation rules
check('title', 'The title field id required')
.not()
.isEmpty(),
check('excerpt', 'The excerpt field id required')
.not()
.isEmpty(),
check('body', 'The full text field id required')
.not()
.isEmpty()];
// create new post
router.post('/', validationRules, postsController.addPost);
controllers/front-end/posts.js
exports.addPost = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
console.log(errors.array());
}
if (!errors.isEmpty()) {
res.render('admin/addpost', {
layout: 'admin/layout',
website_name: 'MEAN Blog',
page_heading: 'Dashboard',
page_subheading: 'Add New Post',
errors: errors
});
req.flash('danger', errors);
req.session.save(() => res.redirect('/dashboard'));
} else {
const post = new Post();
post.title = req.body.title;
post.short_description = req.body.excerpt
post.full_text = req.body.body;
post.save(function(err){
if(err){
console.log(err);
return;
} else {
req.flash('success', "The post was successfully added");
req.session.save(() => res.redirect('/dashboard'));
}
});
}
}
Everything else seem ok, at least from the code you posted.
it's not a bug with express-validators, it is the way how validators work in case of middlewares.
At the root level create a directory called utils and inside the directory a validation.js file and add your validation code in it:
utils/validation.js
const { check } = require('express-validator');
exports.addPostCheck = [
check('title', 'The title field id required')
.not()
.isEmpty(),
check('excerpt', 'The excerpt field id required')
.not()
.isEmpty(),
check('body', 'The full text field id required')
.not()
.isEmpty()
];
In the routes/dashboard.js include validation.js
const validator = require('../../utils/validation.js');
Change Line No: 16
From:
router.post('/post/add', dashboardController.addPost);
To:
router.post('/post/add', validator.addPostCheck, dashboardController.addPost);
In the controllers/admin/dashboard.js
Change Line No: 2
From:
const { check, validationResult } = require('express-validator');
To:
const { validationResult } = require('express-validator');
Remove Line Nos 29 to 39.
Reference
Maybe you should start with this https://express-validator.github.io/docs/ then gradually customise it to fit your need so that you can catch any error along the way.
use this code in index.js to get flash error messages locally in EJS ,
app.use(function (req, res, next) {
res.locals.messages = req.flash();
next();
});
I have applied the solution provided by Saravanakumar T N with a small modification in messages.ejs.
I have: this in the controller:
exports.addPost = (req, res, next) => {
const errors = validationResult(req);
const post = new Post();
if (!errors.isEmpty()) {
req.flash('danger', errors.array());
req.session.save(() => res.redirect('../addpost'));
} else {
post.title = req.body.title;
post.short_description = req.body.excerpt
post.full_text = req.body.body;
post.save(function(err) {
if (err) {
console.log(err);
return;
} else {
req.flash('success', "The post was successfully added");
req.session.save(() => res.redirect('/dashboard'));
}
});
}
}
In the view:
<div id="messages" class="text-center">
<% Object.keys(messages).forEach(function (type) { %>
<% messages[type].forEach(function (message) { %>
<div class="alert alert-success <% if (type === 'danger') { %> alert-dismissible <% } %> alert-<%= type %>">
<% if (type === 'danger') { %>
<button type="button" class="close" data-dismiss="alert">×</button>
<%= message.msg %>
<%} else { %>
<%= message %>
<% } %>
</div>
<% }) %>
<% }) %>
</div>

Trying to add a product to list in NodeJS ("TypeError undefined")

I have just started NodeJS, so I'm on a beginner level. I am trying to build a shopping list app with MongoDB, in which user logs in, creates a list and then adds items to that list. I can register & log in and create a list, but when I try to add items to it, that's when I run into this:
"TypeError: Cannot read property 'products' of undefined".
Code for it is:
const user = req.user;
const shoppinglist_id = req.body.shoppinglist_id;
const name = req.body.name;
let new_product = product_model({
title: req.body.title,
imagePath: req.body.imagePath,
quantity: req.body.quantity
});
new_product.save().then(() => {
shoppinglist_id.products.push(new_product);
console.log('product saved');
shoppinglist_id.save().then(() => {
return res.redirect('/');
});
}); ```
User model:
const user_schema = new Schema({
name: {
type: String,
required: true
},
shoppinglists: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'shoppinglist',
req: true
}]
});
Shoppinglist model:
const shopping_list_schema = new Schema({
name: {
type: String,
required: true
},
products: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'product',
req: true
}]
});
Product model:
var schema = new Schema({
imagePath: {type: String, required: true},
title: {type: String, required: true},
quantity: {type: Number, required: true}
});
Any idea what I'm doing wrong? I know the problem is with this line:
"shoppinglist_id.products.push(new_product);", I have tried everything on it, "user.shoppinglists.products" etc. Nothing works. Any help?
EDIT:
I'll also post as a reference my add shoppinglist, which is working fine.
const user = req.user;
let new_shoppinglist = shopping_list_model({
name: req.body.name,
products: req.body.products
});
new_shoppinglist.save().then(() => {
console.log('shoppinglist saved');
user.shoppinglists.push(new_shoppinglist);
user.save().then(() => {
return res.redirect('/');
});
});
}```
EDIT #2:
I am adding how I am getting to shoppinglist-view:
´´´
const get_shoppinglist = (req, res, next) => {
const shoppinglist_id = req.params.id;
shopping_list_model.findOne({
_id: shoppinglist_id
}).then((shoppinglist) => {
shoppinglist.populate('products')
.execPopulate()
.then((shoppinglist) => {
let data = {
shoppinglist: shoppinglist
};
console.log(data);
let html = shoppinglist_views.shoppinglist_view(data);
res.send(html);
});
});
};´´´
And shoppinglist-view:
´´´
const shoppinglist_view = ((data) => {
let html = `
<html>
<body>
<h1></h1>
<h2>${data.shoppinglist.name}</h2>
<h4>Go back</h4>
<br>
`
data.shoppinglist.products.forEach((product) => {
html += products
html += `
<div>
<p>
<h2>Name of the shopping list: ${shoppinglists.shoppinglist.name}</h2>
<h3> Name: ${product.title}<br></h3>
<img src="${product.imagePath}" width="50px" height="50px" />
quantity: ${product.quantity} </p>
</div>
</body>
</html>
`;
});
html += `
<form action="/add-product" method="POST">
<p>Add products</p><br>
Title?<br>
<input type="text" name="title"><br>
Image-link<br>
<input type="img" name="imagePath"><br>
Quantity?<br>
<input type="number" name="quantity"><br><br>
<button type="submit">Add to list</button>
</form>
</html>
</body>`;
return html;
´´´
This is because shoppinglist_id is undefined. At the moment, you derive the value like this:
const shoppinglist_id = req.body.shoppinglist_id;
In other words, req.body has no property called shoppinglist_id. If you print the value of req.body, you will most likely see that it is a string. If you are attempting to send/receive JSON data (which I'm guessing you are) you must first parse the request body:
const data = JSON.parse(req.body);
const shoppinglist_id = data.shoppinglist_id;
Or even better:
const data = JSON.parse(req.body);
const { shoppinglist_id } = data;
Please note that you should always parse untrusted JSON inside a try ... catch block, but that's another lesson for another day.

node.js sorting object (mongoose, express, ejs)

i have this problem sorting my object of data in my index of my blog app.
I have a blog app based on Express using ejs and mongoDB using mongoose.
What i want is sorting the results so the newest post starts at the top. At this moment it will show the first post at the top.
app.js / mongoose schema
blogSchema = new mongoose.Schema({
title: String,
image: String,
body: String,
created: {type: Date, default: Date.now}
});
var Blog = mongoose.model("Blog", blogSchema);
app.js / Index route
app.get("/blogs", (req, res)=>{
Blog.find({}, (err, blogs)=>{
if(err){
console.log("Error!");
console.log(err);
} else {
res.render("index", {blogs: blogs});
}
});
});
index.ejs foreach
<% blogs.forEach(function(blog){ %>
<img alt="img" src="<%= blog.image %>">
<%= blog.title %>
<span><%= blog.created.toDateString() %></span>
<p><%- blog.body.substring(0, 200) %>...</p>
Read More
<% }) %>
Does anyone have a clue how i can do this?
You can use the sort() method of Mongoose:
Blog.find((err, blogs) => {
if (err) {
console.log(err);
} else {
res.render("index", { blogs: blogs });
}
}).sort({ created: 'desc' });

Node.js blog post add comment not working

When I add a comment to any blog post it doesn't work. This is a normal Node.js MVC controller which is linked to route:
commentBlog(req, res) {
const comment = {
comment: req.body.comment,
author: req.params.id
}
Comment.create(comment, (error, comment) => {
if (error) {
console.log(error);
} else {
Blog.findById(req.params.id, (error, blog) => {
blog.comments.push(comment);
console.log(comment);
blog.save((error, savedBlog) => {
if (error) {
console.log(error);
} else{
res.redirect('/blogs/' + blog._id);
}
})
})
}
})
};
( this is the model )
--------------------
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
var commentSchema = new Schema({
author: { type: Schema.Types.ObjectId, ref: 'user' },
comment: {type: String},
created: {type: Date, default: Date.now},
blog: { type: Schema.Types.ObjectId, ref: 'blog' }
});
var Comment = mongoose.model('comment', commentSchema);
module.exports = Comment;
this is the ejs file
---------------------
<body>
<% if(blog) { %>
<form action="/blogs/<%= blog._id %>/comment" method="POST">
<textarea name="comment[text]"
rows="10" cols="50">Write something here
</textarea>
<input type="submit" value="Post comment">
</form>
<% } %>
**i don't know why it doesn't display when i add like to any post it just
don't work**
Well, you were not displaying the blog content in your ejs file. Only rendering the form. You should render the blog fields for them to display. Below is an example to show the blog title( provided there is a title field in the blog object).
<body>
<% if(blog) { %>
<h2><%= blog.title %> </h2>
<form action="/blogs/<%= blog._id %>/comment" method="POST">
<textarea name="comment[text]"
rows="10" cols="50">Write something here
</textarea>
<input type="submit" value="Post comment">
</form>
<% } %>
You can then display the other fields in your blog object using the example above.

Expressjs Retrieving Collections Based on Parameter

I am trying to loop my database collections based on a parameter request for one of my schema properties within a view. I have a tags property within my schema that is saved as an array. What I want to be able to do is click on one of the values saved to that array, which will render a page with collections that contain the selected tags value. I have been able to create the route, which will direct me to the individual tag that was clicked, but I receive a tags: [xxx, xxx, xxx] has no method .forEach.that occurs when I call my loop within my view. Why would this be and how should I solve this?
Error message:
TypeError: /Users/user/Desktop/Projects/node/blog/views/pages/tag.ejs:15
13| <div class="col-md-12">
14| <h1><%= blogpost.tags %></h1>
>> 15| <% blogpost.forEach(function(blogpost) { %>
16| <%= blogpost.title %>
17| <% }); %>
18| </div>
Object { _id: 54c7bd20c58f389232000001,
category: 'Analytics/SEO/SEM',
content: '',
tagline: 'yep',
author: 'Author',
blogUrl: 'roger',
featureImage: '/images/event-placeholder.png',
title: 'Roger',
__v: 0,
date: Tue Jan 27 2015 11:29:48 GMT-0500 (EST),
tags: [ 'wolf', ' cow', ' monkey' ] } has no method 'forEach'
Here is my model:
var mongoose = require('mongoose');
var mongoosePaginate = require('mongoose-paginate');
var Schema = mongoose.Schema;
var BlogPostSchema = new Schema({
title: { type: String, unique: true },
featureImage: String,
blogUrl: String,
author: String,
tagline: String,
category: String,
content: String,
tags: { type: Array, lowercase: true },
date: { type: Date, default: Date.now() }
});
BlogPostSchema.post('init', function (post) {
var date = new Date(post.date || Date.now() );
post.dateString = date.getMonth() + 1 + '/' + date.getDate() + '/' + date.getFullYear();
});
BlogPostSchema.plugin( mongoosePaginate );
var Blogpost = mongoose.model("Blogpost", BlogPostSchema);
module.exports = mongoose.model('Blogpost', BlogPostSchema);
Here is how I'm calling my individually selected tag within my route (Does my function use the correct parameter call method?):
router.route('/admin/posts/create')
// START POST method
.post(function(req, res) {
console.log("New instance");
console.log(req.body.tags);
var blogpost = new Blogpost(); // create a new instance of a Blogpost model
blogpost.title = req.body.title; // set the blog title
blogpost.featureImage = req.body.featureImage; // set the blog image
blogpost.blogUrl = blogpost.title.toLowerCase().replace(/\s+/g,"-");
blogpost.author = req.body.author; // set the author name
blogpost.tagline = req.body.tagline; // set the tagline
blogpost.content = req.body.content; // set the blog content
blogpost.category = req.body.category; // set the category
blogpost.tags = req.body.tags.trim().split(","); // set the tags
//Save Blog Post
blogpost.save(function(err) {
if (err)
res.send(err);
res.redirect(303, '/'); //NEEDS TO BE CHANGED
});
}) // END POST method
.get(isLoggedIn, function(req, res, blogpost) {
res.render('pages/blogpost-create', {
blogpost : blogpost
});
});
function getTagCriteria(params){
return {
tags: params.blogpost_tags
};
}
router.route('/tag/:blogpost_tags')
.get(function (req, res) {
var tagCriteria = getTagCriteria(req.params);
Blogpost.findOne(tagCriteria, function (err, blogpost) {
if (err)
res.sent(err);
res.render('pages/tag', {
blogpost : blogpost
})
})
});
pages/tag view file:
<div class="container">
<div class="col-md-12">
<h1><%= blogpost.tags %></h1>
<% blogpost.forEach(function(blogpost) { %>
<%= blogpost.title %>
<% }); %>
</div>
</div>
Change your mongo query to:
Blogpost.find(tagCriteria, function (err, blogpost) {
if (err)
res.sent(err);
res.render('pages/tag', {
blogpost : blogpost
})
})
NOTE: blogpost will now be an array [] instead of an object {}
Then in your jade, do the following
<% blogpost.forEach(function(post) { %>
<%= post.title %>
<% }); %>

Resources