Express - Serve folders based on request - node.js

For ExpressJs and NodeJs
Assume I have 3 types of users in my application.
Based on type of user(extracting from cookie), how to serve particular folder based on condition?
Say I have 3 folders x, y and z.
I have condition which says for user_type x -> serve folder x contents.
Same with y and z.
I tried following code but it didn't worked.
function checkCookieMiddleware(req, res, next) {
const req_cookies = cookie.parse(req.headers.cookie || '');
if(req_cookies.type){
if(req_cookies.type === "X"){
express.static(basePath + "/client/x");
}
else if(req_cookies.type === "Y"){
express.static(basePath + "/client/y");
}
else {
next();
}
}
else {
next();
}
}
app.use(checkCookieMiddleware, express.static(basePath + "/client/z"));

I found this NPM package - express-dynamic-static - that looks to do what you are looking for. If you don't want to pull in another dependency, the source code for it is fairly small, you could copy it as a custom middleware yourself.
If you were to use it, then I think you code might look something like this:
const express = require('express');
const dynamicStatic = require('express-dynamic-static')();
const app = express();
app.use(dynamicStatic);
function checkCookieMiddleware(req, res, next) {
const req_cookies = cookie.parse(req.headers.cookie || '');
if (req_cookies.type) {
if (req_cookies.type === 'X') {
dynamicStatic.setPath(basePath + '/client/x');
} else if (req_cookies.type === 'Y') {
dynamicStatic.setPath(basePath + '/client/y');
} else {
// Z
dynamicStatic.setPath(basePath + '/client/z');
}
}
next();
}
app.use(checkCookieMiddleware);

Related

How can I split my koa routes into separate files? Middleware problem

Am trying to split koa routes into separate files.
I'm having folder structure like this for routes.
routes/
__index.js
auth.js
user.js
So if trying with method one means it's working perfectly. But going with dynamic way that is method 2 means it's not working properly. All routes getting hitting, that's not the problem, but at the same time for auth route also it's going inside middleware.isAuthorized.
Method 1
const routesToEnable = {
authRoute: require('./auth'),
userRoute: require('./user')
};
for (const routerKey in routesToEnable) {
if (routesToEnable[routerKey]) {
const nestedRouter = routesToEnable[routerKey];
if (routerKey == 'authRoute') {
router.use(nestedRouter.routes(), nestedRouter.allowedMethods());
} else {
router.use(middleware.isAuthorized, nestedRouter.routes(), nestedRouter.allowedMethods());
}
}
}
module.exports = router;
Method 2
fs.readdirSync(__dirname)
.filter(file => (file.indexOf(".") !== 0 && file !== '__index.js' && file.slice(-3) === ".js"))
.forEach(file => {
// console.info(`Loading file ${file}`);
const routesFile = require(`${__dirname}/${file}`);
switch (file) {
case 'auth.js':
router.use(routesFile.routes(), routesFile.allowedMethods());
break;
default:
router.use(middleware.isAuthorized, routesFile.routes(), routesFile.allowedMethods());
break;
}
});
module.exports = router;
How can i use method two without middleware for auth route itself. Can anyone please suggest what I'm doing wrong here. Thanks in advance.
Issue solved as by own. Previously i used to combine routes with middleware also in the same line.
router.use(middleware.isAuthorized, routesFile.routes(), routesFile.allowedMethods());
But that's the wrong way I used to define route. router.use() uses the middleware to all the routes. So now i just splitted my routes into separate router use with individual path. Mentioned in the document Koa router
Solved answer
fs.readdirSync(__dirname)
.filter(file => (file.indexOf(".") !== 0 && file !== '__index.js' && file.slice(-3) === ".js"))
.forEach(file => {
const routesFile = require(`${__dirname}/${file}`);
if (file !== 'auth.js') {
routesFile.stack.forEach(elem => { router.use(elem.path, middleware.isAuthorized); });
}
router.use(routesFile.routes(), routesFile.allowedMethods());
});

Express middleware infinite loop

Express middleware runs into an infinite loop for encoded URLs,
I have french letters in my URL, so every time I try to hit something a URL like /équipement
The express server runs into an infinite loop trying to process the URL(/%C3%A9quipement/) and I get the ERR_TOO_MANY_REDIRECTS error.
This doesn't happen on my local environment, happens on the dev environment hosted on Azure and has an IIS web server running.
Have 3 middlewares configured,
server.js
app.use(middlewares.ipcheck)
app.use('/:lang*', middlewares.redirection)
app.use(microcache.cacheSeconds(600, middlewares.microcache))
Based on the Azure logs, the code hits the microcache middleware and then goes back to the ipcheck middleware.
ipcheck.js
if (req.query.skipGeo || req.cookies.geoCheck || isBot(req)) {
console.log(`Cookie Present \n`)
let hour = 3600000
res.cookie('geoCheck', 'passed', { maxAge: 365 * 24 * hour })
next()
} else {
console.log(`Checking against mmdb \n`)
const isValidIP = await user.hasValidIp(req.headers['x-forwarded-for'] || req.connection.remoteAddress)
process.env.isValidIP = isValidIP
if (!isValidIP) {
console.log(`Geo Block \n`)
res.status(307).sendFile(path.resolve('public/geo_block.html'))
} else {
console.log(`Success \n`)
next()
}
}
microcache.js
return config.useMicroCache.get() && `${req.hostname}${req.originalUrl}` // useMicroCache is a bool val set based on env.
redirection.js
const params = req.params
let lang = params.lang
const site = utils.user.currentSite(req)
if (!req.originalUrl.includes('api')) {
if (!validLanguage(site, lang)) {
lang = req.cookies.lang && validLanguage(site, req.cookies.lang) ? req.cookies.lang : site.languages.default
config.useMicroCache.set(false)
res.redirect(301, `/${lang}${req.originalUrl}`)
return
} else if (lang !== req.cookies.lang) {
config.useMicroCache.set(false)
} else {
config.useMicroCache.set(true)
}
if (!isLowerCase(req.path)) {
let parsedUrl = url.parse(req.originalUrl)
parsedUrl.pathname = parsedUrl.pathname.toLowerCase()
res.redirect(301, url.format(parsedUrl))
return
}
}
next()

NodeJS RESTful API - How to handle 'undefined' request variables properly?

I am developing a RESTful API using NodeJS and Express.
I noticed that incoming requests sometimes lack of some expected variables, which cause the program to crash, saying it couldn't set the value of a variable, to an 'undefined' value - as no value arrived with the request.
Example:
The application is expecting variableY, but instead variableX is being sent:
formData: { variableX: 'valueX' }
The program is expecting to receive variableY, with the following code:
const checkVariables = Joi.validate({
variableY: req.body.variableY,
}, schema);
The application crashes with the following error:
TypeError: Cannot read property 'variableY' of undefined
I thought about a few ways to handle that, including declaration of variables upon application initiation and using them along, using try-catch.
Another way will be to use if-else, if-chaining, or case-switch, but as you understood of course I am looking for the cleanest way to achieve that.
Any ideas?
Thank you.
** EDIT **
Progressed and managed to achieve the result using the object only. Once trying to reach any of it's inner fields the error will be thrown anyway, example:
if(req.body.variableY == undefined){console.log('The expected variable is undefined');} //true
When the validation addresses a field inside the 'undefined' object:
if(req.body.variableY.dataId == undefined){console.log('The expected variable is undefined');} //crashes
The following error is being thrown again:
TypeError: Cannot read property 'variableX' of undefined
After doing some more digging around, found this Stackoverflow thread:
How to check if object property exists with a variable holding the property name?
Tried using hasOwnProperty, but the same kind of error is being thrown:
TypeError: Cannot read property 'hasOwnProperty' of undefined
Tried wrapping variable declaration using try-catch, still didn't work:
try{
var variableX = req.body.variableX
var variableXDataId = req.body.variableX.dataId
}
catch(e){
res.status(400).send('Wrong request error: Please check your request variables and try again');
}
As this is a really basic validation that should be addressed by most of the RESTful APIs (validating that you get the expected incoming variables inside the request, so the program won't crash by having errors it can't handle - what is the common solution for such problems (expected / unexpected request validation)?
Thank you.
You can take another approach, check req.body before you reach checkVariables:
let body = req.body;
// data - your req.body
// requiredKeys - is an array of strings , [ key1, key2 ... keyN] | string[]
const setKeys = ( data, requiredKeys )=>{
if( !typeof requiredKeys.length ){
requiredKeys = [];
}
if(requiredKeys.length) requiredKeys.forEach( k =>{
k = k.replace(/\+/g,'/');
let keysList = [];
if( /\/+/g.test(k)){
keysList = k.split('/');
}else{
keysList = [k];
}
let [firstKey, ...rest] = keysList;
if( typeof data[firstKey] === 'undefined' ){
data[firstKey] = {};
}
if( rest.length ){
data[firstKey] = setKeys(data[firstKey], [rest.join('/')] );
}
})
return data;
}
let checkedData= setKeys(body, ['variableT','variableP/noname/emptyObj','custom/object/does/not/exist/but/it/will/be/created/here']);
const checkVariables = Joi.validate(checkedData, schema);
UPDATE
Below you will find an working example on how things should work during a /(let's say /usersStatus/:id ) request:
const express = require('express')
const app = express()
const port = 3000
const setKeys = (data, requiredKeys) => {
if (!typeof requiredKeys.length) {
requiredKeys = [];
}
if (requiredKeys.length) requiredKeys.forEach(k => {
k = k.replace(/\+/g, '/');
let keysList = [];
if (/\/+/g.test(k)) {
keysList = k.split('/');
} else {
keysList = [k];
}
let [firstKey, ...rest] = keysList;
if (typeof data[firstKey] === 'undefined') {
data[firstKey] = {};
}
if (rest.length) {
data[firstKey] = setKeys(data[firstKey], [rest.join('/')]);
}
})
return data;
}
/**
* Mock some data
*/
const getUserData = (req, res, next) => {
if (typeof req.body === 'undefined') {
req.body = {};
}
req.body = {
variableY: {
someName: 23
},
variableZ: {
name: 3,
type: {
id: 5,
typeName: 'something',
tags: ['a', 'b', 'c']
}
}
};
console.log('Middleware 1 getUserData');
next();
}
/**
* 1. Setup our middleware for checking keys
* "requiredKeys" is an array of strings
*/
const middlewareSetKeys = (requiredKeys, wrappedMiddleware) => {
return (req, res, next) => {
console.log('Middleware 2 middlewareSetKeys');
if (typeof req.body === "undefined") {
console.log('Leaving Middleware 2 since we don\'t have req.body');
next();
}
/**
* Update "req.body" with keys that we want to have available
* in our next middleware
*/
req.body = setKeys(req.body, requiredKeys);
if (typeof wrappedMiddleware === 'function') {
return wrappedMiddleware.call(this, req, res, next);
} else {
next();
}
}
}
/**
* 2. Let's assume a "user status" situation
* 2.1. We need userInfo from database
* 2.2. Some info won't be retrieved, unless the user accesed some parts of the website to trigger some mechanisms that allows those fields to be exposed, therefore the lack of keys
* 2.3. But we know those keys/objects, and we still want to be present so our code won't crash.
*/
// lets call our getUserData
app.get(
'/', // this path is for some userInfo
getUserData, // this returns userInfo and appends it to `req.data`
middlewareSetKeys([
'userActivity/daily/jobs', // these won't exist in getUserData because the user is lazy and he didn't apply for any JOBS
'userStatus/active/two-weeks-ago', // these won't exist in getUserData because the user joined two days ago. BUT WE STILL NEED IT coz reazons.
]), // We set our desired-later-to-use keys
(req, res, next) => {
/**
* 3. Now our req.body will have our keys
* even if they didn't exist in the getUserData middleware
*/
console.log('Middleware 3 Your middleware');
console.log(req.body);
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(req.body, null, 2))
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
you can use express validator https://www.npmjs.com/package/express-validator
to validate incoming request.Then add this to your controller where a,b,c ,d are parameters you want to valaidate
const nonEmptyFields = ['a', 'b', 'c', 'd'];
nonEmptyFields.forEach(field => req.assert(field, `${field} cannot be blank`).notEmpty());
const errors = req.validationErrors();
if (errors) {
return res.status(400).send(errors);
}
for validating a field inside a field you can try doing this
typeof(req.body && req.body.name !== undefined)
A solution will be to set a default empty object to replace undefined at a parent level:
// checking for body.variableX.variableZ with object destructuring ES6
const {body = {}} = request;
const {variableX = {}, variableY} = body;
const {variableZ} = variableX.variableZ;
// or prior ES6
var body = request.body || {};
var variableX = body.variableX || {};
var variableY = variableX.variableY;
// or in a statement
var variableY = request.body && request.body.variableX ? request.body.variableX.variableY : undefined;
Based on that you can create your own function like getValue(request, 'body.variableX.variableY') to return null if any parent or the end value is undefined:
// asumes the value in the path is either object or undefined
function getValue(rootObj, path = '') {
const parts = key.split('.');
let value = rootObj || {};
let part;
while ((part = parts.shift()) && value !== null) {
value = value[part] || null;
}
return value;
};

How to check the req.url path is existing in app?

I'm using sequelizejs, nodejs in my application. I know this will check inbuild, but I want to check manually like in if() condition.
Below is some url path
/user
/user/11d9b6130159 => user/:id
/user/11d9bdfg0159/sample => user/:id/sample
what I want is, there is Middleware, have to check current url these in app route like
if(url.parse(req.url).path === "/user"){
//some action do
}
But I'm failing remaining urls. Please suggest the way to solve. Thanks
If you really want to do the URL parsing manually then the aproach could be like this:
EDIT: Based on your comment, I modified the sample code (more than 3 levels). You can easily extend it based on your needs.
const url = require('url');
const path = ctx.request.href;
const pathName = url.parse(path).pathname;
const pathNameParts = pathName.split('/'');
if (pathNameParts && pathNameParts[1] && pathNameParts[1] === 'user') {
if (pathNameParts[2]) {
const id = pathNameParts[2]; // :id is now defined
if (pathNameParts[3] && pathNameParts[3] === 'sample') {
if (pathNameParts[4]) {
const id2 = pathNameParts[4]; // :id2 is now defined
if (pathNameParts[5] && pathNameParts[5] === 'disable') {
// do some action for /user/:id/sample/:id2/disable
} else {
// do some action for /user/:id/sample/:id2
}
} else {
// do some action for /user/:id/sample
}
} else {
// do some action for /user/:id
}
} else {
// do some action for /user
}
}
So I would do this only, if you really want to do the parsing yourself. Otherwise use something like express router or koa router. Using express router it would be like:
app.use('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id);
next();
});

regarding foodme project in github

hello i have a question regarding the foodme express example over github:
code:
var express = require('express');
var fs = require('fs');
var open = require('open');
var RestaurantRecord = require('./model').Restaurant;
var MemoryStorage = require('./storage').Memory;
var API_URL = '/api/restaurant';
var API_URL_ID = API_URL + '/:id';
var API_URL_ORDER = '/api/order';
var removeMenuItems = function(restaurant) {
var clone = {};
Object.getOwnPropertyNames(restaurant).forEach(function(key) {
if (key !== 'menuItems') {
clone[key] = restaurant[key];
}
});
return clone;
};
exports.start = function(PORT, STATIC_DIR, DATA_FILE, TEST_DIR) {
var app = express();
var storage = new MemoryStorage();
// log requests
app.use(express.logger('dev'));
// serve static files for demo client
app.use(express.static(STATIC_DIR));
// parse body into req.body
app.use(express.bodyParser());
// API
app.get(API_URL, function(req, res, next) {
res.send(200, storage.getAll().map(removeMenuItems));
});
i don't understand where is the api folder. it doesn't exist and i don't understand how information is going in and out from there. i can't find it.
can someone please explain this to me?
another question:
there is a resource for the restaurant
foodMeApp.factory('Restaurant', function($resource) {
return $resource('/api/restaurant/:id', {id: '#id'});
});
and in the restaurant controller there is a query:
var allRestaurants = Restaurant.query(filterAndSortRestaurants);
and the following lines:
$scope.$watch('filter', filterAndSortRestaurants, true);
function filterAndSortRestaurants() {
$scope.restaurants = [];
// filter
angular.forEach(allRestaurants, function(item, key) {
if (filter.price && filter.price !== item.price) {
return;
}
if (filter.rating && filter.rating !== item.rating) {
return;
}
if (filter.cuisine.length && filter.cuisine.indexOf(item.cuisine) === -1) {
return;
}
$scope.restaurants.push(item);
});
// sort
$scope.restaurants.sort(function(a, b) {
if (a[filter.sortBy] > b[filter.sortBy]) {
return filter.sortAsc ? 1 : -1;
}
if (a[filter.sortBy] < b[filter.sortBy]) {
return filter.sortAsc ? -1 : 1;
}
return 0;
});
};
the things that isn't clear to me is:
how is that we are giving the query just a function without even activating it.
as i understand we should have passed the query somthing like:
{id: $routeParams.restaurantId}
but we only passed a reference to a function. that doesn't make any sense.
could someone elaborate on this?
thanks again.
var API_URL = '/api/restaurant';
var API_URL_ID = API_URL + '/:id';
var API_URL_ORDER = '/api/order';
These lines are just defining string constants that are plugged into Express further down. They're not a folder.
app.get(API_URL, function(req, res, next) {
res.send(200, storage.getAll().map(removeMenuItems));
});
So this function call to app.get(API_URL... is telling Express "Look out for GET requests that are pointed at the URL (your app's domain)/api/restaurant, and execute this function to handle such a request."
"api" is not a folder.
Every requests will pass through the app.get method.
This method will respond to the routes /api/restaurant as defined in the API_URL variable.

Resources