Making ajax response global and usable in all routes - javascript

I am writing a node js express api application which needs to a couple of ajax calls in the beginning of the application when it starts up and it needs to use this "global" data for all the requests it receives.
In the beginning of my app.js, I have
var users = require('./modules/users');
in my users.js, I make the ajax calls to retrieve my users.
//ajax call 1
request.post({url:url1, formData: data}, function optionalCallback(err, httpResponse, body) {
//some code
 and ajax call 2
request.get(url2, function(error, response, body) {
var response = JSON.parse(body);
var users = response.users;
 // I want to make this users global and available in all routes 
}).auth(null, null, true, access_token);

});
My route looks like this
router.post('/create', users.validate, function(req, res, next) {
// ...
}
and users.js is a module where I want access to the users list.
var users = {
validate: function () {
return function (req, res, next) {
// Global variable
var user_list; // This should be the response from the ajax requests
var user_id = req.query.user_id;
return user_list.indexOf(user_id) != -1
}
}
}
module.exports = users;
How do I make the users global so that I can use it in each route?
If there is an alternate better way of doing this, please suggest.

You can create a module for storing the user information. You can attain the functionality by simply using global variables in NodeJS, but I like this approach because its modular and extensible for checks and hooks down the development line.
Below is a simple example.
user-store.js
var userData = null;
module.exports = {
setUserData : function (data){
userData = data;
},
getUserData : function (){
return userData;
}
};
In your AJAX call, you can cache the store the data in the module by doing something like this as you need:
users.js
var userStore= require('./user-store');
ajaxCall()
.then((response) => {
var users = response.users;
userStore.setUserData(users);
});
To use the user data globally, you can import user-store.js and call getUserData function.
wherever-you-need.js
var userStore= require('./user-store');
var userData = userStore.getUserData();
Let me know if this approach serves the purpose for you.

Related

Node.js express - get route params without going into the route function for quick analytics tracking

I aim to send an analytics event for each route in my API. As opposed to saving an event with the entire full route that was called, I want to save the base url as the event and the parameters of the route as variables of the event. For example, when saving an analytics event...
Not this:
{
event_title: "API User Event"
category: "domain.com/api/user_routes/route_1/value_of_param_one"
}
But this:
{
event_title: "API User Event"
category: "domain.com/api/user_routes/route_1"
params: {
param_one: "value_of_param_one"
}
}
I'd like to have a global function that gets the parameters from the request variable, however, if you do this on a higher level (not route level)
app.use('/api/user_routes/*', myWrapperFunction)
myWrapperFunction will detect anything after /api/user_routes as parameters. From my experiments, I was only able to successfully detect the actual parameters inside a specific route function. However, that approach requires me to either edit each route function or wrap it like so...
router.get('/route_1/:param_one', (req, res) => Utility.analyticsEvent(userController.routeOneFunction, req, res));
router.get('/route_2/:param_one', (req, res) => Utility.analyticsEvent(userController.routeTwoFunction, req, res));
router.get('/route_3/:param_one', (req, res) => Utility.analyticsEvent(userController.routeThreeFunction, req, res));
Is there a way to detect the actual parameters of the route without actually going into the function itself? Or is this a limitation on express because it won't know the specifics of the route until it finds the first matching option traversing down the routes?
Edit If there is no way to know the parameters before express matches the specific route: is there a function that you can run before executing the route function that will tell you which route will be matched and will specify the parameters?
Welcome all comments!
I think one approach is to write a middleware like below.
// This will get executed before every request. As we'll add this with app.use() with top level middlewares
function customMiddleware (req, res, next) {
let url = req.baseUrl;
// some if else logic to re-route
if( url.includes('/api/user_routes')) {
let urlSplit = url.split();
if( url[urlSplit.length() - 1] == 'param_one' ) {
res.redirect('/api/user_routes/route_1')
}
else if(url[urlSplit.length() - 1] == 'param_tow' ) {
res.redirect('/api/user_routes/route_1')
}
// and so on
} else {
// executes other middleware or go to matching route
next()
}
}
app.use(customMiddleware)
Found a way to do it after the call is made by overwriting the response.json function
app.use(function (req, res, next) {
var json = res.json;
res.json = function (body) {
// Send analytics event before returning response
try {
// Routes that don't need to be tracked with analytics
let notIncludeRoutes = [
"some_not_tracked_route"
];
let route = req.baseUrl + req.route.path;
if(notIncludeRoutes.indexOf(route) === -1) {
// Track route and params
let route_params = res.req.params;
Utility.analyticsEvent(route, route_params);
}
} catch(error) {
// Don't block call if there was an error with analytics, but do log it
console.log("ANALYTICS ERROR: ", error);
}
json.call(this, body);
};
next();
});

Why do async functions not work within a controller's get() handler?

I am using Node and Express with the the mssql npm package to connect to an SQL Server database. I do this in my app.js file which sets up a global variable to create a connectionPool to the database like so (I omitted some boilerplate stuff for brevity):
const mssql = require('mssql/msnodesqlv8'); // needs to use msnodesqlv8 driver for Windows Authentication to work
const express = require('express');
const app = express();
const DB_MYDB_Config = {
server: "localhost",
database: "MYDB",
options: {
trustedConnection: true // Windows auth enabled hence no username/password required
}
};
global.MSSQL_MYDB = new mssql.ConnectionPool(DB_MYDB_Config); //connectionPool available to whole app
I have a Model file called offer.js which just does this:
async function getOffersAll() {
await MSSQL_MYDB.connect(); // connects to the database
try {
var result = await MSSQL_MYDB.request(MSSQL_MYDB).query('SELECT Top(1) * FROM [dbo].[Offer]');
return result; // returns the recordset of data
} catch (error) {
console.log(error);
} finally {
if (MSSQL_MYDB){
try {
await MSSQL_MYDB.close(); // closes the DB connection
}
catch (error) {
console.log('Error closing connection');
}
}
}
}
exports.getOffersAll = getOffersAll;
So far so good. I then have a Controller file index.js which doesn't really work (explained with comments):
var router = require('express').Router();
const Offer = require('../models/offer'); // the `offer.js` file
/* the function below works perfectly */
(async function () {
var rsOffersAll = await Offer.getOffersAll();
console.dir(rsOffersAll); // works great! recordset rsOffersAll is printed to console
})();
/* this is where it goes wrong even after commenting out the test function above */
router.get('/', async function(req, res) {
var rsOffersAll = await Offer.getOffersAll(); // this just hangs and eventually I get a login failed error for SQL Server.
console.dir(rsOffersAll);
res.render('index', { pagetitle: 'Homepage'}); // never renders
});
module.exports = router;
My question is why does the first async function() that awaits a result from Offer.getOffersAll() not fail, but the same async function placed within the router.get('/') handler fails with a login error? If I remove the var rsOffersAll = await Offer.getOffersAll(); from the router.get('/') handler then the page renders, but of course I have no data to pass to it.
The exact same thing happens even if I store the test function's value in a variable and try to put it in the router.get() handler like this:
async function getOffersTest() {
return await Offer.getOffersAll();
}
router.get('/', async function(req, res) {
var rsOffersAll = await getOffersTest(); // still breaks
console.dir(rsOffersAll);
res.render('index', { pagetitle: 'Homepage'}); // still never renders
});
My ultimate question how do I fix this so it just works the way it should in that when the homepage is visited, the router waits for the data to be returned from the database and then I can pass it to my view or just print to the console if I want?
because of this line global.MSSQL_MYDB = new mssql.ConnectionPool(DB_MYDB_Config);,
when you execute this code outside of router,
(async function () {
var rsOffersAll = await Offer.getOffersAll();
console.dir(rsOffersAll); // works great! recordset rsOffersAll is printed to console
})();
getOffersAll has access to global variable,
and you can successfully connect with db in line await MSSQL_MYDB.connect(); //
but as for router, global scope is the current module.exports object, not the global object.
Solution
you can set MSSQL_MYDB in app like this,
app.set('MSSQL_MYDB')
then you can get this same variable in following function like this
router.get('/', async function(req, res) {
const MSSQL_MYDB = req.app.get('MSSQL_MYDB')
var rsOffersAll = await getOffersTest(MSSQL_MYDB );
console.dir(rsOffersAll);
res.render('index', { pagetitle: 'Homepage'}); // still never renders
});
This whole problem was just solved and it is a bug or something in node mssql package. It only works if you provide a username and password. If you use options: {trustedConnection: true } to enable windows authentication, then the router can never log in. I don't know why this is the case, but if you just supply a username and password (I used sa for testing) then it works fine.

Pass data from express route into node module export function

I am new to node, I think I need to use middleware, but I can't warp my head around what it is actually used for, or if this is where it is meant to be used. I have data that is being posted from my view into an express route.
ROUTE - route.js
var GetPlayer = require('./models/getPlayer.js');
module.exports = function(app) {
app.post('/api/getPlayer', function(req, res) {
//GetPlayer.apiGetPlayer(req.body.username);
console.log(req.body.username); //this logs the correct data
});
}
but now I need to pass that data into a node api call and send that response back to the client. But I can not get the route to call that function or pass the data into it.
MODULE.EXPORT - getPlayer.js
module.exports = {
apiGetPlayer: function(error, res) {
console.log("in get player");
console.log(res);
}
}
You would only want to use an Express middleware if this is something you want to do for more than one route (ie. parsing request body's from JSON to actual Object using body-parser). That seems like it could be overkill based on the supplied code. One way to approach this is to just take the username and pass a callback function in to getPlayer. Then when the callback function passed to apiGetPlayer() returns, respond back to the requester based on the result of apiGetPlayer().
getPlayer.js
module.exports =
// callback is an error-first callback function
apiGetPlayer: function(username, callback) {
let err;
let player;
// Logic for getting player go here
// If an error occurs return an error to the callback
if (err)
return callback(err, null);
return callback(null, player);
}
}
/api/getPlayer route
app.post('/api/getPlayer', (req, res) => {
GetPlayer.apiGetPlayer(req.body.username, (err, player) => {
if (err)
return res.status(500).send(err);
return res.status(200).send(player);
});
});

Break program flow with express.js

I wonder what would be the proper way of breaking the flow of the program and redirect to some route.
Something like the equivalent of a header redirection but using route names instead of the complete URL.
This is quite common to be found in PHP frameworks and quite powerful, and I haven't found yet the way of doing it with express.js.
Imagine this scenario:
function getUser() {
//...
//getting user..
//Here I would like to break the flow of the program
if (error) {
res.redirect('add');
}
//If there was no error we keep the normal flow
//...
}
router.get('/add', function (req, res, next) {
var user = getUser();
var date = //whatever
var type = //whatever
var event = createEvent(user, date, type);
res.render('add', {
date: date,
type: type,
whatever: whatever,
csrfToken: req.csrfToken(),
message: req.flash('error')
});
});
A normal render wouldn't be what I want, because I don't want having to get again all the variables needed to render the view, I would like the route I want to redirect to deal with it.
I was thinking about using writeHead, but that would require the whole URL, not just the route URL.
Any ideas or recommendations?
You could return false or null or some other value from getUser() to indicate a break. Or check the state of the response (e.g. check res._headerSent === true or res.finished === true).
Another possibility might be to look into making getUser() a middleware function. Example:
function getUser(req, res, next) {
// ...
if (error)
return res.redirect('add');
// If there was no error we keep the normal flow
// ...
// you would add user data to `req` here instead of returning it
req.user = ...
next();
}
router.get('/add', getUser, function(req, res, next) {
var user = req.user;
var date = //whatever
var type = //whatever
var event = createEvent(user, date, type);
res.render('add', {
date: date,
type: type,
whatever: whatever,
csrfToken: req.csrfToken(),
message: req.flash('error')
});
});

LoopBack: cannot call method 'post' of undefined

I am new to loopback and node.js.
I have created two models: Rating and RatingsAggregate
using the loopback explorer, I can query and post against the API just fine.
I am try to setup some basic business logic so I am editing the file Rating.js in common/models
Here is the content of it:
module.exports = function(Rating) {
Rating.afterRemote('**', function(ctx, inst, next) {
var loopback = require('loopback');
var app = loopback();
var ratingsaggregate = app.models.ratingsaggregate;
ratingsaggregate.post({"source":"foobar","restaurantID":"foobar","itemMenuName":"foobar","itemSectionName":"foobar","itemName":"foobar","nRatings1":123,"nRatings2":123,"nRatings3":123,"nRatings4":123,"nRatings5":123,"hasImage":true,"imageSize":123,"latestImageRatingID":"foobar","imageCount":123,"lastUpdated":"foobar"}, function(err, response) {
if (err) console.error(err);
next();
});
});
};
I can load my API, but whenever I run a get statement against it, I get this error:
TypeError: Cannot call method 'post' of undefined
My guess is that somehow ratingsaggregate never gets a value... but I don't know what I am doing wrong. Obviously this is not the end state of my business logic, but I am trying some basic CRUD right now between two models
And... here is the answer. There was a getModel function hidden in the documentation
module.exports = function(Rating) {
Rating.afterRemote('create', function(ctx, inst, next) {
var loopback = require('loopback');
var ratingsaggregate = loopback.getModel('ratingsaggregate');
ratingsaggregate.create({"source":"foobar","restaurantID":"foobar","itemMenuName":"foobar","itemSectionName":"foobar","itemName":"foobar","nRatings1":123,"nRatings2":123,"nRatings3":123,"nRatings4":123,"nRatings5":123,"hasImage":true,"imageSize":123,"latestImageRatingID":"foobar","imageCount":123,"lastUpdated":"foobar"}, function(err, response) {
if (err) console.error(err);
next();
});
});
};
Fixes everything and the behaviour is the expected one

Categories