Separate pages for items in MEAN application - node.js

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.

Related

Querying mongoDB return value void in nodeJS app?

I wrote this function to get a document from the end of the collection in MongoDB. When I try calling this function in index.js, it returns undefined.
index.js:
app.get('/', authenticate, function(req, res){
// console.log("DATA: " + getWorldStatsData());
everything = {
worldstats: getWorldStatsData(),
}
res.render('index', {
data: everything
})
})
and my function:
function getWorldStatsData(){
db.collection('worldstats').find({}).sort({"statistic_taken_at":-1}).limit(1).toArray((err, stats)=>{
return stats
})
// return stats
}
As jonrsharpe suggested, this is happening because the code that fetches the data is asyncroous.
That means you need to implement some kind of callback to notify the surrounding function when the operation is complete
A simple example:
index
app.get('/', authenticate, async function(req, res){
// console.log("DATA: " + getWorldStatsData());
everything = {
worldstats: await getWorldStatsData(),
}
res.render('index', {
data: everything
})
})
your funciton:
function getWorldStatsData(){
return db.collection('worldstats').find({}).sort({"statistic_taken_at":-1}).limit(1).toArray((err, stats)=>{
return stats
})
// return stats
}
Please take a look at the link provided by jonrsharpe for a better understanding

How To Bind Node-js DB Query to Web Form

I'm using node and postgres, I'm new to writing async function, what I'm trying to do is a very simple query that will do a total count of records in the database, add one to it and return the result. The result will be visible before the DOM is generated. I don't know how to do this, since async function doesn't return value to callers (also probably I still have the synchronous mindset). Here's the function:
function generateRTA(callback){
var current_year = new Date().getFullYear();
const qry = `SELECT COUNT(date_part('year', updated_on))
FROM recruitment_process
WHERE date_part('year', updated_on) = $1;`
const value = [current_year]
pool.query(qry, value, (err, res) => {
if (err) {
console.log(err.stack)
} else {
var count = parseInt(res.rows[0].count) + 1
var rta_no = String(current_year) + '-' + count
callback(null, rta_no)
}
})
}
For the front-end I'm using pug with simple HTML form.
const rta_no = generateRTA(function (err, res){
if(err){
console.log(err)
}
else{
console.log(res)
}
})
app.get('/new_application', function(req, res){
res.render('new_application', {rta_number: rta_no})
});
I can see the rta_no in console.log but how do I pass it back to the DOM when the value is ready?
Based on the ajax call async response, it will update the div id "div1" when it gets the response from the Node js .
app.js
app.get("/webform", (req, res) => {
res.render("webform", {
title: "Render Web Form"
});
});
app.get("/new_application", (req, res) => {
// Connect to database.
var connection = getMySQLConnection();
connection.connect();
// Do the query to get data.
connection.query('SELECT count(1) as cnt FROM test ', function(err, rows, fields) {
var person;
if (err) {
res.status(500).json({"status_code": 500,"status_message": "internal server error"});
} else {
// Check if the result is found or not
if(rows.length==1) {
res.status(200).json({"count": rows[0].cnt});
} else {
// render not found page
res.status(404).json({"status_code":404, "status_message": "Not found"});
}
}
});
// Close connection
connection.end();
});
webform.pug - Via asynchronous call
html
head
script(src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js')
script.
$(document).ready(function(){
$.ajax({url: "/new_application", success: function(result){
$("#div1").html(result.count);
}});
});
body
div
Total count goes here :
#div1
value loading ...
That seems okay, I'm just not sure of this:
The result will be visible before the DOM is generated
This constraint defeats the purpose of async, as your DOM should wait for the returned value to be there. Instead of waiting for it you could just render the page and once the function returns and runs your callback update the value.
Also, perhaps it's worth having a look into promises

how to send a message from inside a callback function with mongodb in nodejs

I am coding a basic project manager, nothing fancy. I am writing the page where the project is created (with AngularJS) and am sending all the $scope to /create (the backend is Express.js). The router gets the JSON perfectly, and save it to a local MongoDB without problems.
My problem is that I want to set a message telling that the project was created successfully and send it back to AngularJS. This is my code.
router.js
module.exports = function(app, db) {
app.post('/create', function (req, res) {
var create = require("./../scripts/create")(req, res, db);
console.log(create); //just for testing whether I can receive the message.
});
}
create.js
module.exports = function(req, res, db) {
db.collection('projects').insert(req.body.project, function(err, docs) {
if (err) throw err;
return 'Project created.'; //I want to return this string.
});
};
I don't know how to return something from inside the db.collection.insert's callback function.
So you have to remember that anonymous function calls in JavaScript are not assigned to anywhere. They are passed, and then lost. This is usually why we don't really have return statements in them.
var x = function () { return y }; gives x the value of y but since there is never an assignment of the value of a callback, a return statement is meaningless. Callbacks, no matter if they have a return value, will not give you a value. They may feed that return value up to the function that they were given to, but they are entirely lost to you.
The way to get around this is to do some trickery with the scope. Basically what you want to do is 'bump' the value you want to return up a scope you can assign and then return it there. For example, you can do this:
module.exports = function(req, res, db) {
var stringToReturn;
db.collection('projects').insert(req.body.project, function(err, docs) {
if (err) throw err;
stringToReturn = 'Project created.'; //I want to return this string.
});
return stringToReturn;
};
This will work because the return value gets bound to module.exports, which is in turn bound to the result of
var create = require('./create');
console.log(create('something')) //should log 'Project created.'
Solved!!
Router.js
module.exports = function(app, db) {
app.post('/create', function(req, res) {
var create = require("./../scripts/create")(req, res, db);
});
});
Create.js
module.exports = function(req, res, db) {
db.collection('projects').insert(req.body.project, function(err, records) {
if (err) throw err;
res.send("Project created.");
});
};
Now Angular is receiving the response from the server.

Cannot read property 'title' of undefined. Express

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.

rendering results of multiple DB/mongoose queries to a view in express.js

given the async nature of mongoose (or sequelize, or redis) queries, what do you do when you have multiple queries you need to make before rendering the view?
For instance, you have a user_id in a session, and want to retrieve some info about that particular user via findOne. But you also want to display a list of recently logged in users.
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
})
}
// here, current_user isn't populated until the callback fires
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
});
};
So res.render is in the first query callback, fine. But what about waiting on the response from findOne to see if we know this user? It is only called conditionally, so I can't put render inside the inner callback, unless I duplicate it for either condition. Not pretty.
I can think of some workarounds -
make it really async and use AJAX on the client side to get the current user's profile. But this seems like more work than it's worth.
use Q and promises to wait on the resolution of the findOne query before rendering. But in a way, this would be like forcing blocking to make the response wait on my operation. Doesn't seem right.
use a middleware function to get the current user info. This seems cleaner, makes the query reusable. However I'm not sure how to go about it or if it would still manifest the same problem.
Of course, in a more extreme case, if you have a dozen queries to make, things might get ugly. So, what is the usual pattern given this type of requirement?
Yep, this is a particularly annoying case in async code. What you can do is to put the code you'd have to duplicate into a local function to keep it DRY:
exports.index = function (req, res) {
var current_user = null
Player.find({last_logged_in : today()}).exec(function(err, players) {
if (err) return res.render('500');
function render() {
res.render('game/index', { title: 'Battle!',
players: players,
game_is_full: (players.length >= 6),
current_user: current_user
});
}
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
current_user = player
}
render();
})
} else {
render();
}
});
};
However, looking at what you're doing here, you'll probably need to look up the current player information in multiple request handlers, so in that case you're better off using middleware.
Something like:
exports.loadUser = function (req, res, next) {
if (req.session.user_id) {
Player.findOne({_id : req.session.user_id}).exec(function(err, player) {
if (err) return;
if (player) {
req.player = player
}
next();
})
} else {
next();
}
}
Then you'd configure your routes to call loadUser wherever you need req.player populated and the route handler can just pull the player details right from there.
router.get("/",function(req,res){
var locals = {};
var userId = req.params.userId;
async.parallel([
//Load user Data
function(callback) {
mongoOp.User.find({},function(err,user){
if (err) return callback(err);
locals.user = user;
callback();
});
},
//Load posts Data
function(callback) {
mongoOp.Post.find({},function(err,posts){
if (err) return callback(err);
locals.posts = posts;
callback();
});
}
], function(err) { //This function gets called after the two tasks have called their "task callbacks"
if (err) return next(err); //If an error occurred, we let express handle it by calling the `next` function
//Here `locals` will be an object with `user` and `posts` keys
//Example: `locals = {user: ..., posts: [...]}`
res.render('index.ejs', {userdata: locals.user,postdata: locals.posts})
});
Nowadays you can use app.param in ExpressJS to easily establish middleware that loads needed data based on the name of parameters in the request URL.
http://expressjs.com/4x/api.html#app.param

Resources