I've spent quite a bit of time looking to see if there is anything available that will run blocks of route level middleware conditionally.
Ideally the solution would require no changes to the middlewares, and they could just be passed in as an array.
I have just implemented a very crude solution but wanted to see what people think, what issues I'm likely to face and whether there is just a better solution to this.
An example of the problem I am trying to solve is middleware1 altered something on the request that meant middleware2, 3 & 4 do not need to run.
Here's my crude solution:
function conditional({ condition, field, value }, middleware) {
const conditions = {
eq: (_this, _that) => {
return _this === _that;
},
};
return (req, res, originalNext) => {
if (!conditions[condition](_.get(req, field), value)) {
return originalNext();
}
let i = 0;
function next() {
if (i === middleware.length) {
return originalNext();
}
if (typeof middleware[i] === 'function') {
i += 1;
middleware[i - 1](req, res, next);
}
}
next();
};
}
app.get(
'/test-conditional/:condition',
middleware1,
conditional({ condition: 'eq', field: 'params.condition', value: 'run' }, [
middleware2,
middleware3,
middleware4,
]),
middleware5,
async (req, res, next) => {
res.sendFile(path.join(__dirname + '/index.html'));
}
);
Any feedback or pointers in the right direction would be much appreciated.
Related
I am new to node js programming and trying to develop an API using node js, I am able to retrieve the expected output from the built API but I would like to perform some exception handling. For that I would like to check whether the request params coming from URL are not null. Below is my code:
async function getDetails(input) {
// using knex to execute query
return queries.getbymultiwhere('table_name',{
name:input.name,
id:input.id,
role:input.role
})
}
router.get('/:name/:id/:role',(req,res)=>{
getDetails({
name:req.params.name,
id:req.params.id,
role:req.params.role}).then(Results=>{ Do something with results}
})
In above code I want to check that name, id and role param values are not null.
Any helpful solution will be appreciated.
Thank you!
You can create a middleware which checks those parameters.
function check(fields) {
return (req, res, next) => {
const fails = [];
for(const field of fields) {
if(!req.query[field]) {
fails.push(field);
}
}
if(fails.length > 0){
res.status(400).send(`${fails.join(',')} required`);
}else{
next();
}
};
}
app.get('/api', check(['name', 'id', 'role']), (req, res) => {
getDetails()...
});
groups.js
class groupsCtrl {
constructor() {
this.info = "test";
}
get(res, req) {
console.log("LOG ! ", JSON.stringify(this));
}
}
module.exports = new groupsCtrl(); //singleton
routes.js
var express = require('express');
var router = express.Router();
var groupsCtrl = require('controllers/api_admin/groups.js');
router.get('/groups/', groupsCtrl.get);
This logs LOG ! undefined
How can I have access to this in my controller class ?
You need to bind the method to the instance.
One solution:
router.get('/groups/', groupsCtrl.get.bind(groupsCtrl));
Another solution:
constructor() {
this.info = "test";
this.get = this.get.bind(this);
}
Or use something like es6bindall (which basically does the same as the code above, but is perhaps a bit more useful when you need to bind more than one method).
class groupsCtrl {
constructor() {
this.info = 'test';
}
get = (res, req) => {
console.log('LOG ! ', JSON.stringify(this));
};
}
You can just use arrow function to avoid boilerplate code
2020 Update
While both solutions posted by #robertkiep get the job dont, I want to emphasize that both look ugly and are not maintainable
Method 1 which does router.get('/groups/', groupsCtrl.get.bind(groupsCtrl)) looks really ugly when you have a large number of routes
Method 2 gets cumbersome when your controller has many routes
Since your example has only 1 route let me illustrate the problem
Using method 2
class AuthController {
constructor({ db, pgp, logger }) {
super({ db, pgp, logger })
this.postLogin = this.postLogin.bind(this)
this.postLogout = this.postLogout.bind(this)
this.postSignup = this.postSignup.bind(this)
this.postForgot = this.postForgot.bind(this)
this.getReset = this.getReset.bind(this)
this.postReset = this.postReset.bind(this)
}
postLogin(req, res, next) {
}
postLogout(req, res, next) {
}
async postSignup(req, res, next) {
}
async postForgot(req, res, next) {
}
async getReset(req, res, next) {
}
async postReset(req, res, next) {
}
}
Each time you add a new method, the constructor needs to be updated further
Method 3
This in my opinion is a lot cleaner, doesnt need maintenance and you can keep adding methods as you want
The idea is to use the Object.hasOwnPropertyName to get an array of all method names and then bind them programmatically
For example if you write Object.hasOwnPropertyName(AuthController.prototype) it will give you ALL NON STATIC methods in an array
In the example above you will get ['constructor', 'postLogin', 'postLogout'...]
If you call Object.hasOwnPropertyName(AuthController) you get STATIC methods
Lets invoke them programmatically
This controller requires little to no maintenance except keep the static and non static methods in mind, remove the constructor by filtering it out and then invoke bind on each
class AuthController {
constructor({ db, pgp, logger }) {
super({ db, pgp, logger })
this.postLogin = this.postLogin.bind(this)
this.postLogout = this.postLogout.bind(this)
this.postSignup = this.postSignup.bind(this)
this.postForgot = this.postForgot.bind(this)
this.getReset = this.getReset.bind(this)
this.postReset = this.postReset.bind(this)
Object.getOwnPropertyNames(AuthController.prototype)
.filter((propertyName) => propertyName !== 'constructor')
.forEach((method) => (this[method] = this[method].bind(this)))
}
postLogin(req, res, next) {
}
postLogout(req, res, next) {
}
async postSignup(req, res, next) {
}
async postForgot(req, res, next) {
}
async getReset(req, res, next) {
}
async postReset(req, res, next) {
}
}
The answer above is great, I want to add a little bit to help clarify:
Assume we have a class:
class TClass {
constructor(arg) {
this.arg = arg
}
test() {
console.log(this.arg)
}
}
This will NOT work:
const t = new TClass("test")
const method = t.test // method is simply a reference without context
method() // 'this' is not defined
This will work:
const t = new TClass("test")
t.test() // log 'test'
And the reason is like the comments above, the reference to the function doesn't have a context
I am new using nodejs and express
and was wondering what is the best practice to manipulate data before render a view. Currently i want to set some variables based on the retrieved data to render the view. So far this is what i have, but I am not sure if this is the best practice or if there is any better way to do it.
var request = require('request');
module.exports = function(req, res, next) {
request.get('http://nout.nout-app.com/getAccessVIPForUserId/' + req.params.id, function(err, resp, body) {
var bodyResp = JSON.parse(body);
bodyResp.data.forEach(function(el, index, array){
if(el.access_status === '1') {
el.status = 'success';
} else {
el.status = 'warning';
}
if(el.access_friend === '1') {
el.access_friend = 'yes';
} else {
el.access_friend = 'no';
}
});
console.log(bodyResp.data);
if(err || (typeof bodyResp.data === 'undefined' || bodyResp.data === null)) {
res.render('error', {
message: bodyResp.reason ? bodyResp.reason : 'Something went wrong',
error: {
status: 500
}
});
} else {
res.render('profile', {
intern: true,
user: req.user,
invitations: bodyResp.data
});
}
});
};
I appreciate if you guys could give me guide in this and also suggest some good material to improve. Regards.
Yes, .forEach is blocking (synchronous), but it is extremely fast. In general you don't need to worry about for basic data manipulation like that. Remember - async doesn't make something take less time, it just gives other things the ability to keep happening in the mean time.
If you really want to make your loop async, have a look at the async module. async.each is an async version of .forEach
I'm very new to node.js, specifically express.js. I'm working on express.js app to export some rest apis and I found that I have many dup codes that I would like to avoid this issue. I'm seeking a best practice solution. This is my made-up examples:
// app/routes/category.js
exports.create = function (req, res) {
categoryModel.create(someData, function (error, category) {
if (Util.isError(error)) {
res.send(400, error);
} else {
res.send(category);
}
});
};
// app/routes/product.js
exports.create = function (req, res) {
productModel.create(someData, function (error, product) {
if (Util.isError(error)) {
res.send(400, error);
} else {
res.send(product);
}
});
};
You can see that both my create routes codes are pretty much the same but just different model (product vs category) my questions are:
1) is there a way to do inheritance in node.js/express.js?
2) is there a way to do interface in node.js/express.js?
3) what is the best way to avoid duplicate code as above samples?
4) I was thinking of it is possible to have parent class and let both category and product classes inherits from parent. I really do not how it should be done in node.js
I read couple of node.js books but I don't see authors mentioned much about how to architect app in correct or at least best practice.
I read about Prototype.js, is it the right path to go?
Any thoughts and helps would be very appreciated!
There's no need for inheritance (especially since handlers have to be functions so it's hard for me to see how you can use inheritance here). You can simply create a function generator:
var generate_handler = function(model) {
return function(req, res) {
model.create(someData, function (error, obj) {
if (Util.isError(error)) {
res.send(400, error);
} else {
res.send(obj);
}
});
};
};
and then you do
// app/routes/category.js
exports.create = generate_handler(categoryModel);
// app/routes/product.js
exports.create = generate_handler(productModel);
I'm trying to have a total message count for a user's inbox displayed within my layout. I was thinking that I needed to use Express' dynamicHelpers to do this, but in Express <= 2.x, these are not async calls, and I need to do some async processing within them: in this case, a database call with a callback.
I'm trying the following to place the count within my session, which itself is put in a dynamicHelper accessible to the views. However, due to the asynchronous nature of these callbacks, session.unreadMessages is always undefined.
messageCount: function(req, res) {
var Messages = require('../controllers/messages'),
messages = new Messages(app.set('client'));
if(req.session.type === 'company') {
messages.getCompanyUnreadCount(req.session.uid, function(err, result) {
req.session.unreadMessages = result[0].unread;
});
} else if(req.session.type === 'coder') {
messages.getCoderUnreadCount(req.session.uid, function(err, result) {
req.session.unreadMessages = result[0].unread;
});
}
return;
}
Is there another or better way to perform this task?
It should be noted that req.session.unreadMessages is defined (at least within that callback), but undefined when session is called using the helper.
Not sure, it it would be a 'best way', but I'm used to using a filter (or a so called middleware) to load data before it reaches the actual destiny, like in:
filters.setReqView = function(req,res,next) {
req.viewVars = {
crumb: [],
flash_notice: augument,
}
somethingAsync(function(err,data){
req.viewVars.someData = data
next()
})
}
app.all('*', filters.setReqView )
// then on my request:
...
res.render('auth/win', req.viewVars )
Refactoring your code you would have:
app.all('*', function(req, res, next) {
if(req.session && req.session.type){
var Messages = require('./controllers/messages'),
messages = new Messages(app.set('client'));
if(req.session.type === 'company') {
messages.getCompanyUnreadCount(req.session.uid, function(err, result) {
req.session.messageCount = result[0].unread;
next();
});
} else if(req.session.type === 'coder') {
messages.getCoderUnreadCount(req.session.uid, function(err, result) {
req.session.messageCount = result[0].unread;
next();
});
}
} else {
next()
}
});