How to call node module in express rendered view? - node.js

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

Related

I'm trying to create a global variable in express (also using mongoose), but the app keeps deleting the variable when the page is refreshed

I'm working on an e-commerce project. I'm trying to create a shopping cart within the app so that people don't accidentally access another user's data in the Mongo database. To do this, I tried setting up a variable as res.locals.cart. This didn't work because I found out from the docs that res.locals expires in each new page.
My next idea was to create an anonymous shopping cart each time app.js started and store it in the global app.locals object. This does work, and in the following code, you can see it returns the model of the shopping cart. But after that, it's undefined as soon as I refresh or go to a new page as seen by console.log. Why is it doing that? How can I make it so that my data stays across the whole app? And I need it to be a variable, so that it changes for each new user. If there are also any NPM packages that solve this problem, that would be helpful to know.
app.locals.cart = Cart.create({}, function (err, newCart) {
if (!err) {
console.log(newCart);
return newCart
}
});
app.get('/cart', function (req, res) {
console.log(app.locals.cart);
res.render('cart')
});
💡 This is not the best practive, but, if you still want to do it, than this is an example code you can see:
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.locals.cart = [];
const item = {
id: 1,
name: 'Testing'
}
const addToCart = function(req, res, next) {
const { username } = req.body;
// req.body.username just for identifier
// you can change it with user id from token or token or whatever you want
if(typeof app.locals.cart[username] === 'undefined') {
app.locals.cart[username] = [];
}
// add item to cart by username / identifier
app.locals.cart[username].push(item);
next();
}
// or if you want to use this add to global, than you can use this middleware
// and now, you can see
// app.use(addToCart);
app.post('/', addToCart, (req, res, next) => {
// console.log
const { username } = req.body;
console.log(app.locals.cart[username])
res.send(app.locals.cart[username]);
})
app.get('/:username', (req, res, next) => {
const { username } = req.params;
console.log(app.locals.cart[username]);
res.send(app.locals.cart[username]);
})
app.listen(3000, () => {
console.log('Server is up');
})
I hope it's can help you 🙏.
I think the way you are trying is not a best practice.
Instead of using the locals try a different approach.
Creating a cart for each user in the database will be better.
You can link the card with the user. And whenever a user makes a request you fetch the cart from DB and do whatever you want.
To do that, you can add a user property to the Cart Schema. Whenever a user signs up, create a cart for it in the DB. When the user checkouts the cart, save the products in the card as another Document, let say an Order in the Orders Collection and clear the cart for future usage.
QUICK DIGEST:
Store any data from Mongoose onto a variable on your middleware and then have that variable read by app.locals or res.locals. The reason for this is because app.locals is changing and your middleware variable isn't, which lets it be read the same way every time. Example:
res.locals.data = middleware.var;
//or
app.locals.data = middleware.var;
Model.findById("model-id", function (err, noErr) {
if (!err) {
middleware.var = noErr //data retrieved from callback being passed into middleware.var
}
});
Here's what I discovered. My code didn't work because Express refuses to store data directly from a Mongoose function. I discovered this by console.logging the data inside the function and again outside it. I'm not sure why Express refuses to take data this way, but that's what was happening.
Cart.create({}, function (err, newCart) {
if (!err) {
app.locals.cart = newCart;
console.log(app.locals.cart);
//Returns Mongoose model
}
});
console.log(app.locals.cart);
//Now returns undefined
I solved this by storing the value to a variable in my middleware object called mids.
Here's the code on my app.js:
mids.anonymousCart();
app.use(function (req, res, next) {
res.locals.userTrue = req.user;
res.locals.cart = mids.cart;
next();
});
And here's the code on my middleware file:
var mids = {
anonymous_id: undefined,
cart: []
};
mids.anonymousCart = function () {
if (typeof mids.anonymous_id === 'undefined') {
Cart.create({}, function (err, newCart) {
if (!err) {
mids.anonymous_id = newCart._id.toString();
Cart.findById(mids.anonymous_id).populate('items').exec(function (err, cartReady) {
if (!err) {
mids.cart = cartReady;
}
});
}
});
}
}
What's happening here is that when app.js first starts, my middleware mids.anonymousCart is run. In my middleware, it checks that the id of Cart model stored as anonymous_id exists or not. It doesn't, so it creates the cart and stores the id. Since this is stored in the middleware and not Express, it can be accessed by any file linked to it.
I then turned the id I got back into a string, so that it could be read in the next function's findById. And if there's any items in the cart, Mongoose will then fill them in using the populate() method. But THE THING TO PAY ATTENTION TO is the way the data received in cartReady is stored in the middleware variable mids.cart, which is then read by res.locals.cart on app.js.

How do I store user action data? i.e userID date/time and action code

The action code being whichever page the user interacts with
please refer to javascript/nodejs code below.
for example at each router.get() if a user is accesing this i need to store his/her userID the dateTime and an action code matching to each router.
router.get('/', function(request, response) {
response.sendFile(path.join(__dirname + '/eventlist.html'));
});
router.get('/watchlist', function(request, response) {
response.sendFile(path.join(__dirname + '/watchlist.html'));
});
router.get('/search', function(request, response) {
response.sendFile(path.join(__dirname + '/search.html'));
});
app.post('/search', function(req,res){
let inputContent = req.body.srchterm;
var sequelize = require('./db');
sequelize.query('SELECT * FROM main_table WHERE jobname = :jobname OR jobstream = :jobstream OR workstation = :workstation ',
{ replacements: { jobname: inputContent, jobstream: inputContent, workstation: inputContent }, type: sequelize.QueryTypes.SELECT }
)
.then(function(searchib) {
console.log(searchib);
if (searchib == "") {
res.send(srcharray);
} else {
var srcharray = [];
searchib.forEach(function(items){
console.log('displaying srchadata');
srcharray.push ({workstation: items.workstation,
jobstream: items.jobstream,
jobdate: (items.jobdate.toLocaleString('en-GB', { timeZone: 'GMT'})),
jobname: items.jobname
});
console.log(srcharray);
});
// return response.json(srcharray);
res.send(srcharray);
}
});
});
app.use('/', router);
Create your own middleware (storeUserActionDataMiddleware) to store the data like below:
function storeUserActionDataMiddleware(req, res, next) {
let data = {
userId: 42 /* get userId somehow */,
dateTime: new Date(),
actionCode: `${req.method} ${req.originalUrl}`,
};
console.log({ data });
// Store user action data here
// store(data);
// then execute routes
next();
}
If you want to store the data only for specific router, then use the middleware at the top of the router like:
// only specific router will store user action data
router.use(storeUserActionDataMiddleware);
router.get(/* ... */)
router.post(/* ... */)
Or, if you want to store the data application wide, then use the middleware at the top of the app like:
// all routes will store user action data
app.use(storeUserActionDataMiddleware);
app.use(/* ... */)
app.get(/* ... */)
app.post(/* ... */)
You need to use middleware, and in the middle ware you need to get the token from the user, where you can decode it for user data, and then store this with timestamp

A passed JSON Object is printed as a [Function] at the destination function

I am fairly new to NodeJS and asynchronous programming. I've never been seriously into JavaScript until I started working on a project that required me to set up a REST API using node server and I started working on it for the past day or two. I'm trying to set up a modular application and have been successful in setting up a route, model and controller using Express JS.
I use express to read the request bodies to get data posted to the server in JSON. But my current situation came up when I tried to pass certain data from my routes module (or file) to my controller in a function call.
When I couldn't read the data I tried to log it and to my surprise the JSON Object that I passed as an argument is now logged as a [Function].
Here is how I pass the data:
routes.js
var express = require('express');
var UserCtrl = require('./controller');
var user = express.Router();
// Define routes
// Request to list all users.
user.get('/', function (req, res){
UserCtrl.getAll(function (result){
res.json(result);
});
});
user.post('/create', function (req, res){
var postData = {
'firstName': req.body.firstName
};
UserCtrl.create(function (postData, result){
res.json(result);
});
});
module.exports = user;
controller.js
var Users = require('./model');
// list all users
module.exports.getAll = function (callback) {
Users.findAll(callback);
}
// create user
module.exports.create = function (postData, callback) {
console.log(postData);
// create user by passing the user to a model
Users.create (newUser, callback);
}
May be I've got the basics wrong or may be this is completely a mistake of my code. Should I make the controller also an express middleware?
This code calls create. The first value sent to it is a function. This function that you send to create takes in a postData and a result value.
UserCtrl.create(function (postData, result){
res.json(result);
});
This code is what is getting called. The first thing that gets sent in is postData. When you called it just above you sent in a function to that first thing. So console.log will output function.
module.exports.create = function (postData, callback) {
console.log(postData);
// create user by passing the user to a model
Users.create (newUser, callback);
}
Perhaps you meant to send in that postData object first?
user.post('/create', function (req, res){
var postData = {
'firstName': req.body.firstName
};
// This is the changed line
UserCtrl.create(postData, function (postData, result){
res.json(result);
});
});

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

How to validate multiple values at once and dose it need for MongoDB

I have the following express POST route handler that accepts GET and POST data something like this:
app.post('/handler/:id/:type', function (req, res, next) {
var id = req.param('id');
var type = req.param('type');
var body = req.body;
// Ho to check req.body params?
var document = _.extend(req.body, {id: id, type: type});
Collection.create(document, function (err, data) {
.....
})
});
Is it problem to don't check incoming parameters and write to MongoDB database as in example above? Or how can check this ones?

Resources