Node app connecting to pugjs - node.js

I'm relatively new to using Node.js. I've successfully created an app that takes user data (specifically the date in which the user was created) and spits out the amount of daily, weekly, monthly and yearly users. This was successfully tested via Postman.
I've successfully installed pugjs and am trying to transfer the calculated data in to the pug file. I have a controller, model and route file all represented below:
userAPIController.js
'use strict';
const mongoose = require('mongoose'),
express = require('express'),
app = express(),
moment = require('moment'),
pug = require('pug'),
User = mongoose.model('Users'),
compiledFunction = pug.compile('results.pug');
exports.list_all_users = function(req, res) {
User.find({}, function(err, user) {
if (err)
res.send(err);
res.json(user);
});
};
exports.create_a_user = function(req, res) {
var new_task = new User(req.body);
new_task.save(function(err, user) {
if (err)
res.send(err);
res.json(user);
});
};
exports.calculate = function(req, res) {
User.find({}, function(err, userArray) { //the second parameter, userArray, is an array that is returned to you.
if (err)
res.send(err);
var daily_count = 0;
var weekly_count = 0;
var monthly_count = 0;
var yearly_count = 0;
var total_count = 0;
for (var i = 0; i < userArray.length; i++) {
total_count++;
var timeString = userArray[i].Created_date;
var dayString = timeString.getUTCDate();
var monthString = timeString.getUTCMonth()+1;
var yearString = timeString.getUTCFullYear();
var todaysDate = new Date();
var todaysDay = todaysDate.getUTCDate();
var todaysMonth = todaysDate.getUTCMonth()+1;
var todaysYear = todaysDate.getUTCFullYear();
// Calculating Daily Users
if (dayString === todaysDay && monthString === todaysMonth && yearString === todaysYear) {
daily_count += 1;
}
// Calculating Weekly Users
var weekDifference = todaysDay - dayString;
if (monthString === todaysMonth && yearString === todaysYear && (weekDifference >= 0 && weekDifference <= 7)) {
weekly_count += 1;
}
// Calculating Monthly Users
if (monthString === todaysMonth && yearString === todaysYear) {
monthly_count += 1;
}
// Calculating Yearly Users
if (yearString === todaysYear) {
yearly_count += 1;
}
}
res.json({
"daily_count":daily_count,
"weekly_count":weekly_count,
"monthly_count":monthly_count,
"yearly_count":yearly_count,
"total_count":total_count
});
});
};
exports.delete_a_user = function(req, res) {
User.remove({
_id: req.params.userId
}, function(err, user) {
if (err)
res.send(err);
res.json({ message: 'User successfully deleted' });
});
};
With a userAPIModel.js, userAPIRoutes.js and server.js file as well, all modeled after this tutorial: https://www.codementor.io/olatundegaruba/nodejs-restful-apis-in-10-minutes-q0sgsfhbd
I also have a views folder with the pug file inside, and am unsure how to take my daily, weekly, monthly and yearly users (as seen in the controller) and transfer them in to the pug. I understand the #{} pug syntax; however I am not sure about how to get the information over. I've tried going in to the controller file where the data is located and using a res.send function such as:
app.get('/', function (req, res) {
res.send(compiledFunction({
dCount: daily_count,
wCount: weekly_count,
mCount: monthly_count,
yCount: yearly_count,
tCount: total_count
}));
})
And then importing it to pug from there. The only concern is, I don't believe this is the correct way to link the controller to the pug file or that I'm using the right function.
What exactly am I doing wrong? I figure I'd put the above in the controller because that's where the JSON is being outputted, but I'm used to putting it in the server file.

I love pug. You probably will, too.
Step 1) At some point after you require pug, and before you start routing, set pug as the view engine.
var app = express();
app.set('view engine', 'pug');
Step 2) Make a views folder in your project root and write a pug template in there. I.E. /project_root/views/home.pug
Step 3) route a request to your template. This is done by using the express method "render". You can attach variables or function results to the data object. In my example, I'm literally calling it "data" but you can reference it however you want so long as it's a javascript object. The key names are important as they're used to reference your data in the pug template.
app.route('/', function(req, res){
res.render('home.pug', {data: someVar});
});
Step 4) Reference your data in your template. Pug allows you to not just reference, but you can iterate over it or perform JavaScript on it. A simple example of what "home.pug" might look like is this:
doctype
html
head
title Test Template
body
// Here it's interpolated in a string
h1 Take a look at my #{data}
// Here the inner html is being set as your variable
h2= data
// Here's an example of your data being set to a tag attribute value using ES6 template strings
a(href=`${data}`) Click Here
Add this to your bookmarks for future reference:
https://expressjs.com/en/guide/using-template-engines.html
Be sure to read up more on pug here:
https://pugjs.org/api/getting-started.html

Related

Passing mongodb data into views through express

I am building a VERY basic application to book a vehicle for hire, I am trying to access data from mongodb, which seems to work, as it outputs to the console on load. I cannot get this information to render in my view. If I set the render method outside of the function scope I cannot access the db data, if I set the render inside then the booking page never loads. I have been messing around with the same block of code for days now and I have had no joy.
//------------------------------------------------THE SET UP--------------------------------------------
// set node dependencies
let express = require("express");
let bodyParser = require("body-parser");
let mongoose = require("mongoose");
let connection = mongoose.connection;
let data = mongoose.connect("mongodb://localhost:27017/test")
// allow for encoding od POST DATA
let urlencodedParser = bodyParser.urlencoded({extended: false});
// set up app to extend express
let app = express();
// view engine
app.set("view engine", "ejs");
// static files
app.use("/assets", express.static("assets"));
//------------------------------------------------------------------------------------------------------
// REDIRECTS BASED ON URL
app.get('/', function(req,res){
res.render("index")
});
app.get('/booking', function(req,res){
res.render("booking",{qs:req.query})
});
app.post('/booking',urlencodedParser, function(req,res){
// Surround this with if !blacklisted to run inner code
if (req.body.blacklist !== "on"){
console.log(req.body);
// booking page takes age details and redirects/queries database accordingly
if (req.body.age >= 25){
connection.once('open', function () {
connection.db.collection("vehicles", function(err, collection){
collection.find({}).toArray(function(err, data){
console.log(data[0]._id); // it will print collection data
})
});
res.render("contact-success",{data:req.body})
connection.close();
});
}
else if (req.body.age < 25 && req.body.age > 17){
connection.once('open', function () {
connection.db.collection("vehicles", function(err, collection){
collection.find({}).toArray(function(err, data){
console.log(data[0]._id + "<25 message"); // it will print collection data
})
})
})
// THIS IS WHERE I WANT TO PASS THE DB DATA INTO.. so that it redirects to this page and filters the vehicles collection appropriately.
res.render("contact-failed",{data:req.body});
}
}
else{
console.log(req.body.firstName , req.body.lastName , "Has been blacklisted!")
res.render("blacklisted",{data:req.body});
}
// else if blacklisted redirect to a sorry, contact company page.
});
let port = 3000;
app.listen(port);
console.log("listening on port " + port);
Few things about your code -
You should call res.render() inside the call to mongodb.
connection.once('open', function () {
connection.db.collection("vehicles", function(err, collection){
collection.find({}).toArray(function(err, data){
console.log(data[0]._id); // it will print collection data
res.render("contact-success",{data:req.body})
});
});
connection.close();
});
You are not checking for errors. If you get any error with query, you will end up with no response to the request. So it's always better to check for errors.
if(err) {
// process the error and send appropriate message.
} else {
// send what you want to send to view.
res.render("contact-success",{data:req.body})
}
You have conditions for
if (req.body.age >= 25){
...
} else if (req.body.age < 25 && req.body.age > 17){
...
}
But there is nothing for plain else. So if any of the two conditions is not met, you will end up with no response to the request.
There should be one res.render() or res.send() in each of the conditions.

How to call node module in express rendered view?

I'm having a really difficult time trying to understand how to pass called data to my view. I'm requiring the Mozscape node module in a little application i'm trying to develop but I don't quite understand how I'm to pass the information into my view. I would imagine that I need to create the object in my router and call the function in my view. Mozscapes returns an object which I would pass into my view and then use Handlebars to iterate through the data?
Here's the code from my router below
//pull in express
const express = require('express');
//declare router as variable for index view
const index = express.Router();
//mozscape for seo data
var Mozscape = require('mozscape').Mozscape;
//mozscape expects accessid and secretid
//only allows for 1 call per 10 seconds with free plan
//need to add to env file later
var moz = new Mozscape('mozscape-6d6ab44235', '846aca62a6db69661c81b784115e8a9');
//dummy data
var business =
{
name: 'Party Corner',
address: '123 Main Street, Red Bank, NJ',
hours: 'Monday through Friday 9 - 5',
website: 'http://www.partycorner.com/',
category: 'Party Supplies',
imgUrl: 'https://scontent.fewr1-3.fna.fbcdn.net/v/t31.0-8/14361226_10154547117788288_58127422291546970_o.jpg?oh=126b8240a8ac27dfb06b57ed51f4320e&oe=5A5A8FCC'
};
//middleware to process data for view
var businessSeo;
businessSeo = function (req, res, next) {
req.businessSeo = moz.urlMetrics(business.website, ['page_authority', 'links', 'domain_authority'], function (err, res) {
if (err) {
console.log(err);
return;
}
console.log('requesting data from moz');
console.log(res);
return res;
});
};
index.use(businessSeo);
//logging the data
console.log(businessSeo);
//use declare router for http methods
//get request for index with request, response, next params
index.get('/', function (req, res, next) {
//render response from server using index view from declared path in app.js
res.render('index', {
//declare {{ title }} used in main template extended as default template
title: "Business Directory",
//use business as object keys and values.. |key val|
business: business,
body:
{
description: 'This is a business for the business directory, how neat is that?'
},
mozData: businessSeo
})
});
module.exports = index;
I'm just trying to log the object in the front end right now and it returns that moz is not defined. I imagine I would need to move my business object into it's own variable (later quired response) and then put the function right into my router and access the business website from there?
Expected output:
Object {pda: 24.123844872381643, uid: 187, upa: 33.43142060578742}
function storeData(param) {
businessSeo = param;
}
//middleware not needed to process data for view
businessSeo = moz.urlMetrics(business.website, ['page_authority', 'links', 'domain_authority'], function (err, res) {
if (err) {
console.log(err);
return;
}
console.log('requesting data from moz');
//console.log(res); sent data to the variable
storeData(res);
});
//use declare router for http methods
//get request for index with request, response, next params
index.get('/', function (req, res, next) {
//data has arrived!
console.log(businessSeo);

Retrieve data from MongoDB and save it to global object in Node.js and Express.js

I'm trying to get data from MongoDB collection and then save it to a global object.Later I need to parse it to HTML template.
Here is my code:
When user log onto his profile: then we need to get his projects and here we call findeprojects() function
usrRouter.route('/profile')
.all(function (req,res,next) {
if(!req.user){
res.redirect('/');
}
next();
})
.get(function (req,res,userObj) {
// var proj = findprojects();
userObj = req.user;
var pro = {};
pro = findprojects(userObj);
res.render('index',{name:userObj.username, email:userObj.email});
//res.sendFile('profile.html',{root:path.join(__dirname,'../public'),},{name:userObj.username});
});
Here is findeprojects function code:
var findprojects = function(obj) {
var usern = obj.username;
mongodb.connect(url,function(err, db){
if(err) throw err;
var collection = db.collection('projects');
//console.log(usern);
collection.find({'pusername':usern});
cursor =db.collection('projects').find({ 'pusername': usern }).toArray(function(err,items){
//console.log(items);
var i;
for(i=0; i<items.length;){
userProjects.createdBy = items[i].pusername;
userProjects.proName = items[i].projectName;
userProjects.proType = items[i].projectType;
userProjects.proDesc = items[i].projectDesc;
//return userProjects;
i = i+1;
}
});
console.log(userProjects);
});
};
I have declared global object at the top like:
userProjects = {
createdBy:'',
proName:'',
proType:'',
proDesc:''
};
But when I console userprojects object after calling the findeprojects() function it displays empty values.
why dont you use mongoose to model your stuff.
its more intuitive and you no need to declare the global object and do the mapping in the for loop that you are doing.
also your approach is a bit wrong in terms of when you iterate through for aren't you overwriting ?
say you have two documents where pusername is abdul.
so in your case you loose first object which will get overwritten by the second one.
i see that you commented out a return statement but even that wont work properly.
from a design point of view your approach is not efficient.
in mongoose you can do:
{
var userProjectSchema = new mongoose.Schema({
createdBy: { type: String }
, proName: String
, proType: String
, proDesc: String
});
// Find a single document by username.
userProjectSchema.findOne({ pusername : 'abdul' }, function(err, resDoc) {
if (err) return console.error(err);
// do your html stuff here
});
// Find all documents.
userProjectSchema.find(function(err, results) {
if (err) return console.error(err);
// do your html stuff here
});
}

node js returning mysql data to variables

I'm trying to use node js and mysql, along with rendering data from my database into a view.
The issue is, how do I render multiple pieces of data within 1 view (Using jade template).
router.get('/about', function(req, res) {
var animalsData = sequelize.query("SELECT * FROM animals").success(function(rows) {
return rows;
});
console.log(animals); // returns 'undefined'
var humansData = sequelize.query("SELECT * FROM humans").success(function(rows) {
return rows;
});
console.log(humans); // returns 'undefined'
res.render('about', { animals: animalsData, humans: humansData });
});
As you can see, how can I get the data and then pass it into the view? Using nested callbacks can get messy if theres 5 or 6 bits of data (mysql queries) I wish to pass into the view?
Thanks
First of, those returns don't work. The code that you are running is asynch. I would suggest you to use promises:
var q = require("q");
router.get('/about', function(req, res) {
var animalDeferer = q.defer(),
humanDeferer = q.defer;
sequelize.query("SELECT * FROM animals").success(function(rows) {
animalDeferer.resolve(rows);
});
sequelize.query("SELECT * FROM humans").success(function(rows) {
humanDeferer.resolve(rows);
});
Q.all([animalDeferer.promsie, humanDeferer.promise]).spread( function(animalsData, humansData) {
res.render('about', { animals: animalsData, humans: humansData });
});
});
I wrote this directly on the answerbox so it may have typos.
q - https://github.com/kriskowal/q

Dynamic routes with Express.js -- is this even possible?

Every time I update the database with a new menu item, I'm trying to get the routing to update with one more route. Here's my sad little ugly attempt:
Here in app.js, I check the menu database and shazaam...routes are made on the fly at startup. Cool!:
// in app.js //
var attachDB = function(req, res, next) {
req.contentdb = db.content;
req.menudb = db.menu;
req.app = app; // this is the express() app itself
req.page = PageController;
next();
};
db.menu.find({}, function (err, menuitems){
for(var i=0; record = menuitems[i]; i++) {
var menuitem = record.menuitem;
app.all('/' + menuitem, attachDB, function(req, res, next) {
console.log('req from app all route: ',req)
PageController.run(menuitem, req, res, next);
});
}
http.createServer(app).listen(config.port, function() {
console.log(
'\nExpress server listening on port ' + config.port
);
});
});
Not real elegant but it's a proof of concept. Now here's the problem: When I save a new menu item in my Admin.js file, the database get's updated, the router seems to get updated but something about the request just blows up after clicking on a menu link with a dynamically created route
Many things in the request seem to be missing and I feel like there is something fundamental I don't understand about routing, callbacks or perhaps this is just the wrong solution. Here's what the function responsible for creating a new menu item and creating a new route in my Admin.js file looks like:
// in Admin.js //
menuItem: function(req, res, callback) {
var returnMenuForm = function() {
res.render('admin-menuitem', {}, function(err, html) {
callback(html);
});
};
var reqMenudb = req.menudb,
reqContentdb = req.contentdb,
reqApp = req.app,
reqPage = req.page;
if(req.body && req.body.menuitemsubmitted && req.body.menuitemsubmitted === 'yes') {
var data = { menuitem: req.body.menuitem };
menuModel.insert( data, function(err) {
if (err) {
console.log('Whoa there...',err.message);
returnMenuForm();
} else {
// data is inserted....great. PROBLEM...the routes have not been updated!!! Attempt that mimics what I do in app.js here...
reqApp.all('/' + data.menuitem, function(req, res, next) {
// the 2 db references below are set with the right values here
req.contentdb = reqContentdb;
req.menudb = reqMenudb;
next();
}, function(req, res, next) {
reqPage.run(data.menuitem, req, res, next);
});
returnMenuForm();
}
});
} else {
returnMenuForm();
}
},
Saving the data in the admin section works fine. If you console log app.routes, it even shows a new route which is pretty cool. However after refreshing the page and clicking the link where the new route should be working, I get an undefined error.
The admin passes data to my Page controller:
// in PageController.js //
module.exports = BaseController.extend({
name: "Page",
content: null,
run: function(type, req, res, next) {
model.setDB(req.contentdb); /* <-- problem here, req.contentdb is undefined which causes me problems when talking to the Page model */
var self = this;
this.getContent(type, function() {
var v = new View(res, 'inner');
self.navMenu(req, res, function(navMenuMarkup){
self.content.menunav = navMenuMarkup;
v.render(self.content);
});
});
},
getContent: function(type, callback) {
var self = this;
this.content = {}
model.getlist(function(records) {
if(records.length > 0) {
self.content = records[0];
}
callback();
}, { type: type });
}
Lastly, the point of error is here in the model
// in Model.js //
module.exports = function() {
return {
setDB: function(db) {
this.db = db;
},
getlist: function(callback, query) {
this.db.find(query || {}, function (err, doc) { callback(doc) });
},
And here at last, the 'this' in the getlist method above is undefined and causes the page to bomb out.
If I restart the server, everything works again due to my dynamic loader in app.js. But isn't there some way to reload the routes after a database is updated?? My technique here does not work and it's ugly to be passing the main app over to a controller as I'm doing here.
I would suggest two changes:
Move this menu attachment thing to a separate module.
While you're at it, do some caching.
Proof of concept menu db function, made async with setTimeout, you'll replace it with actuall db calls.
// menuitems is cached here in this module. You can make an initial load from db instead.
var menuitems = [];
// getting them is simple, always just get the current array. We'll use that.
var getMenuItems = function() {
return menuitems;
}
// this executes when we have already inserted - calls the callback
var addMenuItemHandler = function(newItem, callback) {
// validate that it's not empty or that it does not match any of the existing ones
menuitems.push(newItem);
// remember, push item to local array only after it's added to db without errors
callback();
}
// this one accepts a request to add a new menuitem
var addMenuItem = function(req, res) {
var newItem = req.query.newitem;
// it will do db insert, or setTimeout in my case
setTimeout(function(newItem){
// we also close our request in a callback
addMenuItemHandler(newItem, function(){
res.end('Added.');
});
}, 2000);
};
module.exports = {
addMenuItem: addMenuItem,
getMenuItems: getMenuItems
}
So now you have a module menuhandler.js. Let's construct it and use it in our app.
var menuHandler = require('./menuhandler');
var app = express();
// config, insert middleware etc here
// first, capture your static routes - the ones before the dynamic ones.
app.get('/addmenuitem', menuHandler.addMenuItem);
app.get('/someotherstaticroute', function(req, res) {
var menu = menuHandler.getMenuItems();
res.render('someview', {menu: menu});
});
// now capture everything in your menus.
app.get('/:routename', function(req, res){
// get current items and check if requested route is in there.
var menuitems = menuHandler.getMenuItems();
if(menuitems.indexOf(req.params.routename) !== -1) {
res.render('myview', {menu: menuitems});
} else {
// if we missed the route, render some default page or whatever.
}
});
app.get('/', function(req, res) {
// ...
});
Now you don't go to db if there were no new updates (since menuitems array is always up to date) so your initial view is rendered faster (for that 1 db call, anyway).
Edit: oh, I just now saw your Model.js. The problem there is that this refers to the object you have returned:
{
setDB: function(db) {
this.db = db;
},
getlist: function(callback, query) {
this.db.find(query || {}, function (err, doc) { callback(doc) });
}
}
So, no db by default. And since you attach something to the app in the initial pageload, you do get something.
But in your current update function, you attach stuff to the new app (reqApp = req.app), so now you're not talking to the original app, but another instance of it. And I think that your subsequent requests (after the update) get the scope all mixed up so lose the touch with the actual latest data.
In your code when you start your server it reads from the menu db and creates your routes. When your menu changes, you do not re-read from db again.
I suggest you do something like the following
app.all('*', function(req, res) {
//read from your menu db and do the the route management yourself
});

Resources