Hi im developing nodejs express app. I am getting exception and dont know why. Everything seem to perfect for me. My exception is shown below:
500 TypeError: C:\Nodejs\NodejsBlog\apps\blog/views/postdetail.jade:23<br/> 21| .col- md-12 <br/> 22| .posts <br/> > 23| h3= post.title <br/> 24| p=post.body <br/> 25| p tag: <br/> 26| i=post.tag <br/><br/>Cannot read property 'title' of undefined
21| .col-md-12
22| .posts
> 23| h3= post.title
24| p=post.body
25| p tag:
26| i=post.tag
Cannot read property 'title' of undefined
at eval (eval at (C:\Nodejs\NodejsBlog\node_modules\jade\lib\jade.js:152:8), :221:59)
at C:\Nodejs\NodejsBlog\node_modules\jade\lib\jade.js:153:35
at Object.exports.render (C:\Nodejs\NodejsBlog\node_modules\jade\lib\jade.js:197:10)
at Object.exports.renderFile (C:\Nodejs\NodejsBlog\node_modules\jade\lib\jade.js:233:18)
at View.exports.renderFile [as engine] (C:\Nodejs\NodejsBlog\node_modules\jade\lib\jade.js:218:21)
at View.render (C:\Nodejs\NodejsBlog\node_modules\express\lib\view.js:76:8)
at Function.app.render (C:\Nodejs\NodejsBlog\node_modules\express\lib\application.js:504:10)
at ServerResponse.res.render (C:\Nodejs\NodejsBlog\node_modules\express\lib\response.js:798:7)
at C:\Nodejs\NodejsBlog\apps\blog\routes.js:64:14
at callbacks (C:\Nodejs\NodejsBlog\node_modules\express\lib\router\index.js:164:37)
And here is the app.post code:
app.get('/Post/:id',function(req,res){
var postdata;
var comments;
Post.findOne({_id:req.params.id},function(err, docs){
if(docs) {
postdata=docs;
console.log('Gönderi bulundu');
console.log(docs);
console.log(postdata);
console.log(postdata.title);
} else {
console.log('Gönderi bulunamadı');
}
});
Comment.findOne({postid:req.params.id},function(err, docs){
if(docs) {
console.log('Yorum bulundu');
console.log(docs);
} else {
comments=docs;
console.log('Yorum bulunamadı');
}
});
return res.render(__dirname+"/views/postdetail",{
title: 'adfasdf',
stylesheet: 'postdetail',
post:postdata,
comments:comments
});
});
And my view:
extends ../../../views/bloglayout
block js
script(type='text/javascript')
$(function() {
$("#commentform" ).submit(function( event ) {
alert( "Handler for .submit() called." );
$.ajax({
url: '/Post/Comment/',
type: "POST",
data: $('#commentform').serialize(),
success: function(response){
alert('Yorum Kaydedildi');
}
});
event.preventDefault();
});
});
block content
.row
.col-md-12
.posts
h3=post.title
p=post.body
p tag:
i=post.tag
p Anahtar Kelimeler:
b=post.keywords
.row
.col-md-4
h5 Yorum Yap
form#commentform(role='form',action='/Post/Comment', method='post')
input(type='hidden',name='comment[postid]',value=postdata._id)
.form-group
input.form-control(type='email',name='comment[email]',placeholder='E-posta adresi')
.form-group
input.form-control(type='text',name='comment[website]', placeholder='Website')
.form-group
textarea.form- control(type='text',name='comment[content]', placeholder='Yorum')
button.btn.btn- default(type='submit') Ekle
-comments.forEach(function(comment) {
.well
p
b=comment.content
p=comment.email
-})
Also i checked my mongodb. There is data. I dont know why 'title' property is 'undefined' have no idea.
This is a race condition issue. The two functions that pull from MongoDB are asynchronous and so the call to res.render() happens before the DB returns the data in each function's respective callback. You need to nest each function so that they have access to the proper context. See below:
app.get('/Post/:id', function (req, res, next){
Post.findOne({_id:req.params.id},function(err, postData){
if (err) return next(err);
Comment.findOne({postid:req.params.id},function(err, comments){
if (err) return next(err);
return res.render(__dirname+"/views/postdetail",{
title: 'adfasdf',
stylesheet: 'postdetail',
post:postData,
comments:comments
});
});
});
});
However, you can see how this can get pretty messy as you nest further and further. To prevent this, you can use a control flow library like caolan/async
Side Note:
You're Jade is looking to iterate over a comments array and you are returning a single doc from MongoDB (assuming you are using the mongoose module). You will want to change your Mongoose function from findOne() to simply find() so that mongoose can return an array of docs with the proper postid.
Edit:
Vinayak Mishra is also correct in pointing out that you can use Express' routing middleware as a way to impose control flow within a route. Here is an example:
// Use the app.param() method to pull the correct post doc from the database.
// This is useful when you have other endpoints that will require work on
// a Post document like PUT /post/:postid
app.param('postid', function (req, res, next, id) {
Post.findById(id, function (err, post) {
if (err) return next(err);
if (!post) return next('route');
req.post = post;
});
});
app.get('/post/:postid',
// -- First route middleware grabs comments from post doc saved to req.post
function (req, res, next) {
Comment.find({ postid: req.post.id }, function (err, comments) {
if (err) return next(err);
req.comments = comments;
next();
});
},
// -- This route middleware renders the view
function (req, res, next) {
res.render('/postDetail', {
// ... list locals here ...
});
}
);
Add this in app.js (server.js) or your root file of the app to post req
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
Add this both middleware above on your routes
The two routines to fetch data from db are async routines, hence their callbacks are not invoked by the time you fire res.render(). You will need to wait for the queries to return results, then invoke render.
You can make use of middlewares to do fetching of post and comments in sequence before you're ready to render the post.
Here's an illustration;
app.get('/Post/:id', fetchPost, fetchComments, renderPost, handleErrors);
function fetchPost(req, res, next) {
Post.findOne({
_id: req.params.id
}, function (err, docs) {
if (!err && docs) {
console.log('Gönderi bulundu');
req.postdata = docs;
next();
} else {
console.log('Gönderi bulunamadı');
next(err || new Error('No such post: ' + req.params.id));
}
});
}
function fetchComments(req, res, next) {
Comment.findOne({
postid: req.params.id
}, function (err, comments) {
if (!err) {
console.log('Yorum bulundu');
req.postComments = comments || [];
next();
} else {
console.log('Yorum bulunamadı');
next(err);
}
});
}
function renderPost(req, res, next) {
res.locals.post = req.postdata;
res.locals.comments = req.postComments;
res.locals.title = 'adfasdf - anything that fits';
res.locals.stylesheet = 'postdetail';
return res.render(__dirname + '/views/postdetail');
}
function handleErrors(err, req, res, next) {
// You'll get here only if you recieved an error in any of the previous middlewares
console.log(err);
// handle error and show nice message or a 404 page
res.locals.errmsg = err.message;
res.render(__dirname + '/views/error');
}
Let me know if are unable to follow anything in the code above.
Related
i am trying to assign value of a feild from momgoose to a variable which would be used as value to another feild in another schema which is as follows.
function getNextSequence() {
var x = null
relcounter.findOne({"name":"cabi"},'seq', function (err, configs) {
x=configs.seq
console.log("##"+x)
})
console.log(x)
}
X is coming out to be null
Also i need this value of x to add to another feild which is as follows
relconf.update(
{name:"css"},
{ $push: { relations: {id:getNextSequence(),name:req.body.name,divId:"name_div"} } }
Basically ineed the function getNextsequence to return the value of seq feild and assign to id feild
can anyone suggest proper code and reason for null value for getNextSequence()
It useless wrap it in function, you must create include callbacks or Promises chaining. So you need read about asynchronous JavaScript https://www.sitepoint.com/javascript-goes-asynchronous-awesome/ For example:
//callbacks example
...
app.get('/', function(req, res){
//parse request params
...
relcounter.findOne({"name": name},'seq', function (err, configs) {
if(err) {
throw err;
}
res.send(configs.seq);
});
});
//promise example
...
app.get('/', function(req, res){
//parse request params
...
relcounter.findOne({"name": name},'seq')
.then(function(configs){
res.send(configs.seq);
})
.catch(function(err)=>{
throw err;
})
});
I'm using a generic rest api that allow passing the mongo collection name on the request and serve it's content.
My LIST command looks like that:
router.get('/:collectionName', function(req, res, next) {
req.collection.find().sort('-created_on').exec(function(e, results){
if (e) return next(e);
res.send(results)
});
});
It works great.
My problem is that I want every list query to populate sub objects if exists.
I tried:
req.collection.find().populate().sort..
But obviously I get an error:
TypeError: utils.populate: invalid path. Expected string. Got typeof undefined
Help?
In the end I had to patch it:
router.get('/:collectionName', function(req, res, next) {
var populationQuery = [];
var paths = req.collection.schema.paths;
for (var path in paths) {
if (paths[path].caster) {
populationQuery.push({path: path});
}
}
req.collection.find().populate(populationQuery).sort('-created_on').exec(function (e, results) {
if (e) return next(e);
console.log(results);
res.send(results)
});
});
This works, but I guess there should be some better solution
and thanks to be there.
Issue :
I'm making a tiny mongoose "middleware" to handle a mongoose error :
// callback function called at each mongoDB response
var handleDbRes = function(callback) {
return function (err, entries) {
if (err) {
err.status = 500;
return next(err);
}
return callback(entries) // that line throw the exception
}
};
And so I'm using it into an api endpoint, e.g. :
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res.json))
})
With that code, I encounter an error :
TypeError: Cannot call method 'get' of undefined
I followed the exception and looked at res.json() declaration, when debugging, I figured out :
var app = this.app;
var *** = app.get('***') // that line throw the exception
I guess that app is not defined cause app doesn't exists in "this".
Please can you help me to solve this problem ? I think that the reason is simple but I don't get it...
Thanks you for listening ;)
EDIT : I tried to res.json.bind(res) and it worked, as I thought, but that's really awful to bind this way for most api endpoint and I guess there is another way to do that kind of functionality without that.
EDIT : Thanks to Mscdex advices, I modified my code this way :
.get('/', function(req, res, next) {
models.article.find(handleDbRes(res.json.bind(res), next))
...
...
// callback function called at each mongoDB response
var handleDbRes = function(successCallback, errorCallback) {
return function (err, entries) {
if (err) {
err.status = 500;
return errorCallback(err);
}
return successCallback(entries)
}
};
When you pass res.json, the context for the json() function is lost (it no longer knows what this is because it is not bound). So here are a few possible solutions:
Use a bound version of the function so that this inside json() will always evaluate correctly:
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res.json.bind(res)))
})
Or use a wrapper function instead:
someRouter.get('/', function(req, res) {
function respondJSON(val) {
res.json(val);
}
models.article.find(handleDbRes(respondJSON))
})
Or just pass in res and call res.json() inside handleDbRes():
someRouter.get('/', function(req, res) {
models.article.find(handleDbRes(res))
})
// callback function called at each mongoDB response
var handleDbRes = function(res) {
return function(err, entries) {
if (err) {
err.status = 500;
return next(err);
}
res.json(entries);
}
};
The other problem is that handleDbRes() doesn't have access to next, so you need to also pass that function in for when you run into an error.
I'm writing application with Node, Express, Mongoose and Angular. I can display all items on a page. My next step is to write separate page for items from database, so i create route:
app.get '/products/:product_id', (req, res) ->
Product.findById req.params.product_id, (err, product) ->
res.send(err) if (err)
res.render('product.html', { name: product.name } )
return
return
Everything cool, i can display name with <%= name %> in product.html, but Angular doesn't work. Without '/:product_id' work.
This is my route to get all products:
app.get '/api/products', (req, res) ->
Product.find (err, products) ->
if (err)
res.send(err)
res.json(products); # return all products in JSON format
return
return
And on the front side:
$http.get('/api/products')
.success (data) ->
$scope.products = data
console.log(data)
return
.error (data) ->
console.log('Error: ' + data)
return
So, how i can write application, in which i go to http://mydomain.com/products/53c68728c7e5a5381a000002 then one item will display? (53c68728c7e5a5381a000002 is id)
If I understand correctly, you're not sending the id on the angular side.
In that case, you should create a $resource service :
myApp.factory('ProductService', [ '$resource', function($resource) {
return $resource('http://mydomain.com/products/:id', {
id : '#_id'
});
} ]);
And then call it in your controller :
myApp.controller('MyCtrl', ['$scope', 'ProductService', '$routeParams',
function($scope, ProductService, $routeParams) {
$scope.findOne = function() {
$scope.product = ProductService.get({
id : $routeParams.id
});
};
} ]);
It will only work if you include your service in the controller declaration (aswell as routeParams if it's not there already), and if you have your routes set up correctly on the front-end side :
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/product/:id', {templateUrl: 'partials/product.html', controller: 'MyCtrl'});
}]);
I had to do some reserach to make sure I am not wrong because I don't have a lot of experience with MEAN. However, looking at meanjs and their implementation, it seems to remain the same.
According to the sample articles routes.js https://github.com/meanjs/mean/blob/master/app/routes/articles.server.routes.js
app.route('/articles/:articleId')
.get(articles.read) //<----THIS GUY RIGHT HERE
.put(users.requiresLogin, articles.hasAuthorization, articles.update)
.delete(users.requiresLogin, articles.hasAuthorization, articles.delete);
They do a call to articles.read. So we find that function in the controller section.
https://github.com/meanjs/mean/blob/master/app/controllers/articles.server.controller.js
This function grabs and returns the req.article
exports.read = function(req, res) {
res.jsonp(req.article);
};
Which if you dig deeper is found using mongoose and added to the req.
exports.articleByID = function(req, res, next, id) {
Article.findById(id).populate('user', 'displayName').exec(function(err, article) {
if (err) return next(err);
if (!article) return next(new Error('Failed to load article ' + id));
req.article = article;
next();
});
};
and in the articles angular controller
https://github.com/meanjs/mean/blob/master/public/dist/application.js
$scope.findOne = function () {
$scope.article = Articles.get({ articleId: $stateParams.articleId });
};
You are still returning HTML as part of the request. Let angular handle the routing and pull the data from a request to your node server.
app.get '/products/:product_id', (req, res) ->
Product.findById req.params.product_id, (err, product) ->
res.send(err) if (err)
res.json({ name: product.name })
return
return
Or something to that effect. When your resource or even a simple http request is made to /products/:product_id then whatever you are returning via the res is what it tries to parse. If you are doing res.render it is going to return that as a response and break because you are expecting something else.
EDIT In reference to the comment on what tutorial you are using *
Add to Server Side - This will add an api route to return JSON data for that product/product_id
//View a product
app.get('/api/products/:product_id', function(req, res) {
Product.findOne({
_id : req.params.product_id
}, function(err, product) {
if (err)
res.send(err);
res.json(product);
});
});
Add To Front-end Angular Controller - Do this wherever you are doing your http.get for the product
//id is a placeholder for however you are passing in the product id
$http.get('/api/products/' + id)
.success(function(data) {
$scope.product = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
Still doesn't work.
Routes (backend):
app.get '/products/:product_id', (req, res) ->
Product.findById req.params.product_id, (err, product) ->
res.send(err) if (err)
res.render('product.html', { name: product.name } )
return
return
App.js (Front):
config(['$routeProvider', ($routeProvider) ->
$routeProvider.when('/products/:id', {templateUrl: 'views/product.html', controller: 'StoreControllerOne'});
Controllers.js (Front):
.controller 'StoreControllerOne', ['$scope', '$http', 'ProductService', '$routeParams',
($scope, $http, ProductService, $routeParams) ->
$scope.findOne = ->
$scope.product = ProductService.get({
id : $routeParams.id
});
console.log($scope.product.id)
return
]
Services.js (Front):
.factory('ProductService', [ '$resource', ($resource) ->
return $resource('/products/:id', {
id : '#_id'
});
]);
When i go to www.mydomain.com/products/53c92d5dda9cef6c23000002, JS console said, that can't find www.mydomain.com/products/javascripts/app.js for example, bu this file is in www.mydomain.com/javascripts/app.js. Because adding ../ in html file is not good practice and it not work with directives.
I'm building a web app with Node.js, mongodb, express and jade. When I start the server all is well on the terminal but when I load the webpage it returns a server error regarding a jade template...can you help?
error at /
{ [TypeError: /Users/Web/views/layout.jade:11
9| section.login
10| ul
>11| if session.isLoggedIn
12| li
13| a(href="/logout") Logout
14| else
Cannot read property 'isLoggedIn' of undefined]
Looking at my code on the layout.jade and login.js where I have all the codeI can't find the error. And this is following the exact structure and code of a course I recently followed.
layout.jade
html5
html
head
title= pageTitle
body
header
h1= pageTitle
a(href="/") Home
section.login
ul
if session.isLoggedIn
li
a(href="/logout") Logout
else
li
a(href="/login") Login
li
a(href="/signup") Create Account
section.content
block content
login.js
var mongoose = require('mongoose');
var Member = mongoose.model('Member');
var cleanString = require('../helpers/cleanString');
var hash = require('../helpers/hash');
var crypto = require('crypto');
module.exports = function (app) {
app.get('/signup', function (req, res) {
res.render('signup.jade');
});
// create a new account
app.post('/signup', function(req, res, next) {
var email = cleanString(req.param('email'));
var pass = cleanString(req.param('pass'));
if (!(email && pass)) {
return invalid();
}
Member.findById(email, function (err, member) {
if (err) return next(err);
if (member) {
return res.render('signup.jade', { exists: true });
}
crypto.randomBytes(16, function (err, bytes) {
if (err) return next(err);
var member = { _id: email };
member.salt = bytes.toString('utf8');
member.hash = hash(pass, member.salt);
Member.create(member, function (err, newMember) {
if (err) {
if (err instanceof mongoose.Error.ValidationError) {
return invalid();
}
return next(err);
}
//member created successfully
req.session.isLoggedIn = true;
req.session.member = email;
controle.log('created member: %s', email);
return res.redirect('/');
});
});
});
function invalid() {
return res.render('signup.jade', { invalid: true});
}
});
}
Express does not expose the request object req to the view. You'll have to manually provide these values to the view for example by a middleware like this
app.use(function(req, res, next) {
if (req.user) {
req.locals.isLoggedIn = req.session.isLoggedIn;
req.locals.member = eq.session.member;
}
next();
});
Express copies the req.locals to the oject available in the view's context. In your jade view you can now access member and isLoggedIn:
html5
html
head
title= pageTitle
body
header
h1= pageTitle
a(href="/") Home
section.login
ul
if isLoggedIn
li
a(href="/logout") Logout
else
li
a(href="/login") Login
li
a(href="/signup") Create Account
section.content
block content
I strongly recommend to not expose the full session object to the view but only the fields you really need.
If you want session to be available to the jade template as "session", you need to store it as res.locals.session.
http://expressjs.com/4x/api.html#res.locals also shows how to make a simple middleware that will do this for every page.