node.js middleware to check mandatory parameters - javascript

I'm using node.js and express in my current app.
I have created several middleware functions, each one being created like:
function loadUser(req, res, next){
...
}
I'd like to create a middleware that would check the existence of mandatory params in an express action. For instance, I have a /user/create action which needs nickname, password, email, ... as mandatory parameters. I would then need to pass this list of params to a middleware so it can check if those parameters exist in the req.query.
Any idea ?
UPDATE
I've finally done the following (in express documentation, there is an example of middleware that require additional parameter http://expressjs.com/guide.html#route-middleware).
function checkParams(arr){
return function(req, res, next) {
// Make sure each param listed in arr is present in req.query
var missing_params = [];
for(var i=0;i<arr.length;i++){
if(! eval("req.query." + arr[i])){
missing_params.push(arr[i]);
}
}
if(missing_params.length == 0){
next();
} else {
next(JSON.stringify({ "error" : "query error", "message" : "Parameter(s) missing: " + missing_params.join(",") }));
}
}
}
It is then called like the other middlewares:
app.post('/user/create', checkParams(["username", "password"]), function(req, res){
...
});

Have you tried implementing it as a dynamic helper instead of a middleware? It might work.

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

Expressjs middleware static typed path and path with param conflict

I'm about to implement REST endpoints for authenticated users and non-authenticated users in expressjs. My current understanding of REST has led me to following design pattern:
User resource
Token user:
/users/self GET, PATCH
Non-token user:
/users POST
/users/:user_id GET
Profile image resource
Token user:
/users/self/profile-images POST
/users/self/profile-images/:profile_image_id PUT, DELETE
Non-token user:
/users/:user_id/profile-images GET
I'm struggling to figure out how to use this pattern without having :user_id parameter become self, i.e {user_id: 'self'}. I would want them to act as two isolated path types without interference, one strict and one dynamic. Is this possible? If so, how?
A code example of my current implementation looks like following:
// instPrivateUserRestRoutes.js (token-user)
router.use('/users/self', [
instAccountRestRoutes(restControllers),
instAuthenticationSessionRestRoutes(restControllers),
instPrivateProfileImageRestRoutes(restControllers)
])
// instPublicUserRestRoutes.js (non-token user)
router.use('/users/:user_id', [
instPublicProfileImageRestRoutes(restControllers)
])
// instRestApp.js (mount point for top resources)
router.use(instPrivateUserRestRoutes(restControllers))
router.use(instPublicUserRestRoutes(restControllers))
What you could do it to create a conditional routing middleware. The factory dunction takes as first argument a callback method that determines which router should be use, and as second argument a list of routers, and returns a new middle war that conditionally uses one of the routes.
function conditionalRouting(cond, routes) {
return function (req, res, next) {
try{
// get the index for the router that should be used
var idx = cond(req, res)
// call this router and pass req, res and next to it
routes[idx](req, res, next)
} catch(err) {
// in the case an error occurs in on of the steps "throw" that error
next(err)
}
}
}
You could then use it that way:
app.use(
'/users/:user_id',
conditionalRouting(
(req, _) => req.params.user_id === 'self' ? 0:1,
[routerForSelf, routerForUser]
))
An other way would be to handle this case explicitly with a middle ware that triggers a not found error:
function forceNotFound(req, res, next) {
var error = new Error('resource not found')
error.status = 404
next(error)
}
And add this as your last middleware
router.use('/users/self', [
instAccountRestRoutes(restControllers),
instAuthenticationSessionRestRoutes(restControllers),
instPrivateProfileImageRestRoutes(restControllers),
forceNotFound
])
This way it is clear that express should stop at that point.
This will look different to what the standard Cannot GET /the/path would look like, but you normally don't want to display those default error messages anyway.
If you want to have the same kind of message then you could write:
var parseUrl = require('parseurl')
var encodeUrl = require('encodeurl')
app.use((req, res, next) => {
var msg = 'Cannot ' + req.method + ' ' + encodeUrl(parseUrl.original(req).pathname)
var error = new Error(msg)
error.status = 404
next(error)
})
If you only want to handled numeric ids you could use this:
router.use('/users/:user_id(\\d+)', [
instPublicProfileImageRestRoutes(restControllers)
])
That way instPublicProfileImageRestRoutes would only be called if user_id is numberic.

It is possible to enhance the express.js req and res variables without using a middleware function?

I'm working in a restful service using express.js and i want to enhance the req and res variables so for example you could write something like
app.use(function (req, res, next) {
res.Ok = function (data) {
res.status(200).send(data);
};
res.InternalError = function (err) {
res.status(500).send(err);
};
});
And later
router.get('/foo', function (req, res) {
res.Ok('foo');
})
This will send 'foo' in the body of the response and set the status code to 200 and is working perfectly.
My first question is if it is possible to add such functionality without a middleware function, lets say in a property or the prototype of the app variable?
The second question is if there are performance issues if you add many functionality with middleware functions at the app level. Are this functions attached to the request and response object per request or once on the application startup?
I know the Sails framework already do this but I'm wondering if they use middleware functions as well.
I keep digging and turns out that the request and response object are exposed in express using the __proto__ property.
var express = require('express'),
app = express();
app.response.__proto__.foo = function (data) {
this.status(200).send(data);
};
And later in the router
router.get('/foo', function (req, res, next) {
res.foo('test');
});
This will print test in your browser so it is possible to add functionality without using any middleware.
Note: I'm sure there are some drawbacks to this approach (overwriting express predefined properties, for example) but for testing purposes and adding very simple functionality I think is slightly better in terms of performance.
I'm not aware of any other way than using middleware. But in my opinion you could do the following to achieve nearly the same thing.
// Some Route
router.get('/foo', function(req, res, next) {
// ...
if(err) {
res.status(500);
return next(err);
}
return res.send('ok');
});
// Another route
router.get('/bar', function(req, res, next) {
// ...
if(badUserId) {
res.status(400);
return next('Invalid userId.');
}
req.result = 'hello';
return next();
});
router.use(function(req, res) {
// I prefer to send the result in the route but an
// approach like this could work
return res.send(req.result);
});
// Error Middleware
router.use(function(err, req, res, next) {
if(res.statusCode === 500) {
// Log the error here
return res.send('Internal server error');
} else {
return res.send(err);
}
});

node.js & express: for loop and `app.get()` to serve articles

I'm working on a node js express application that uses app.get() to serve the different web addresses. So app.get('/',{}); serves the home page, app.get('/login'{ }); serves the login page, etc.
Is it good practice to programatically serve pages using a for loop, like in this example?
var a = ["a", "b", "c"];
a.forEach(function(leg) {
app.get('/' + leg, function(req, res){
res.render('index.ejs', {
word : leg
});
});
});
index.ejs is just
<p><%= word %> </p>
So that site.com/a, site.com/b, and site.com/c are all webpages.
I want to utilize this to run .foreach() on a list of all of the article titles (coming from a database of stored articles).
EDIT: The website allows users to submit posts, which become "articles" in a database. I want this loop to route to new submitted articles after they've been posted. If I want to do app.get('/' + newPageName); for user submitted pages AFTER I've already started the server with node server.js, how is that achieved?
Make use of middlewares to better handle the requests. I assume you will have tens/hundreds of posts, adding routes for them like what you've done, is not so elegant.
Consider the following code; I am defining routes of /posts/:legId form. We will match all requests to this route, fetch the article and render it.
If there are handful of routes to be defined you could make use of regular expression to define them.
// dummy legs
var legs = {
a: 'iMac',
b: 'iPhone',
c: 'iPad'
};
app.get('/posts/:leg', fetch, render, errors);
function fetch(req, res, next) {
var legId = req.params.leg;
// add code to fetch articles here
var err;
if (!legs[legId]) {
err = new Error('no such article: ' + legId);
}
req.leg = legs[legId];
next(err);
}
function render(req, res, next) {
res.locals.word = req.leg;
res.render('index');
}
function errors(err, req, res, next) {
console.log(err);
res.locals.error = err.message;
// render an error/404 page
res.render('error');
}
Hope this helps, feel free to ask me any questions you may have.
No, you should not be generating route handlers like that. This is what route parameters are for.
When you start a path component (folder) with a : in an Express route, Express will match any URL that follows the pattern automatically, and place the actual value in req.params. For example:
app.get('/posts/:leg', function(req, res, next) {
// This will match any URL of the form /post/something
// -- but NOT /post/something/else
if (/* the value of req.params.leg is valid */) {
res.render('index.ejs', { word: req.params.leg });
} else {
next(); // Since the user requested a post we don't know about, don't do
// anything -- just pass the request off to the next handler
// function. If no handler responds to the request, Express
// defaults to sending a 404.
}
});
In the real world, you'd probably determine if the leg param is valid by doing a database lookup, which entails making an async call:
app.get('/posts/:leg', function(req, res, next) {
db.query(..., req.params.leg, function(err, result) {
if (err) next(err); // Something went wrong with the database, so we pass
// the error up the chain. By default, Express will
// return a 500 to the user.
else {
if (result) res.render('index.ejs', result);
else next();
}
});
});

NodeJS supertest access to session object

I'm testing my Node.js application with supertest. In my controller I access the session object. In order to make a valid request this session object needs to be filled with some data.
Controller
// determine whether it is user's own profile or not
var ownProfile = userId == req.session.user._id ? true : false;
Test
it('profile', function (done) {
testUserOne.save(function(error, user){
request
.agent(server)
.get('/profile?userId=' + user._id)
.expect('Content-Type', /html/)
.expect(200)
.expect(/Profile/)
.end(done);
})
});
Question
How can I mock the req/session object?
just use as sub-app, and call your authenticated() at parent-app:
var mockApp = express();
mockApp.use(session);
mockApp.all('*', function(req, res, next) {
//authenticated(req, res, next);
//OR
req.session.uid = 'mock uid';
next();
});
mockApp.use(app);
all your routes will be authenticated before matched!
This new lib should do the trick:
https://github.com/rjz/supertest-session
You might also have a look at superagent that is part of supertest. Here's a nice tutorial on this:
http://jaketrent.com/post/authenticated-supertest-tests/
Don't want another lib? Try this one ...
https://gist.github.com/joaoneto/5152248

Categories