How can I make my express route more dynamic - node.js

I want to make my express route have the ability be able to pass anything past the first set of parameters to it
app.get('/views/app/:name/*', appRoutes.partials);
and in my route file I have
exports.partials = function(req, res) {
var name = req.params.name;
var partial = req.params.partial;
var content = "";
res.render('views/app/'+name+'/'+partial,content);
};
I know partial would not be the variable to use there but how can I make whatever passes through the star append to the end?
I hope this makes sense.

The star (*) placeholder will capture all the remaining text and place it under key '0' on params object (if you had more *'s, they would go to '1', '2'...).
So, how about this:
exports.partials = function(req, res) {
var name = req.params.name;
var partial = req.params['0'];
var content = "";
res.render('views/app/'+name+'/'+partial,content);
};
If you expect the wildcard part to be an arbitrary route, you will have to parse it yourself. For example:
// url: /views/app/profile/main/dashboard
exports.partials = function(req, res) {
var path = req.params['0'].split('/');
console.log(path[0], path[1]); //>> main dashboard
};

Related

Restify JsonClient URL x Path

When I create a JsonClient in node I do the following:
var client = restify.createJsonClient({
url: 'https://www.domain.com:4321/api'
});
Once I've done that, I make calls like so:
client.post('/service/path', { });
Which seems right. I expect that the path called would be something like https://www.domain.com:4321/api/service/path. However, what is happening is that the client is throwing away the /api base path and calling https://www.domain.com:4321/service/path.
I don't get it - I'm inserting the client URL into a config file, so that I can change hosts without any hassle; Now that I need a base path, I need to change the code as well as the config.
If you put a wrapper around the restify JsonClient stuff you could do it with minimal code change and the config would, I think, work the way you want it.
Create a library file myClient.js
'use strict';
var restify = require('restify');
var jsonClient = null;
module.exports = {
createJsonClient: function(opts){
var opts = opts || {};
var url = opts.url;
var parts = url.split('/');
var main_url = parts[0] + '//' + parts[2];
var basePath = parts[3] ? parts[3] : '';
jsonClient = restify.createJsonClient({url: main_url});
return {
get: function(path, cb){
var adjusted_path = '/' + basePath + path;
jsonClient.get(adjusted_path, function(err2, req2, res2, obj2){
return cb(err2, req2, res2, obj2);
});
}
}
}
}
Then use it like this.
var myClientWrapper = require('./lib/myClient');
var client = myClientWrapper.createJsonClient({url: 'http://localhost:8000/api'});
client.get('/service/path/one', function(err, req, res, obj){
if(err){
console.log(err.message);
return;
}
console.log(res.body);
});
It could use some more error checking and the url parsing is a little brittle, but it does work. I tried it out. Of course, I only wrapped the get function but you can see how it would work for the others.

Trying to print res.locals variabe in view page(.hbs)

My controllers looks like this in usermains.js
var header = function (req, res, next) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.locals.title="helloooo";
res.write(loadView('headerpage'));
console.log(res.title);
next();
};
app.get('/log',header,values,renderBody);
Here is my loadview function
var loadView = function(name, locals) {
if(name=='main'){
console.log(path.join('views/layouts/'+name + '.hbs'));
var template = fs.readFileSync(path.join('views/layouts/'+name +'.hbs')).toString();
}else{
console.log(path.join('views/'+name + '.hbs'));
var template = fs.readFileSync(path.join('views/'+name + '.hbs')).toString();
}
return template;
};
And i am calling this on view page.
{{title}}
It is giving output as {{title}} on browser. How to get res.locals.title value on view page. Thanks!
Use Handlebars in order to first compile, and then execute the template, passing the appropriate data that you wish. Simply reading/loading the template will give you the raw data. Use something like (tweak it for your needs, this is only a sample):
var handlebars = require('handlebars');
var rawTemplate = fs.readFileSync(path.join('views/'+name + '.hbs')).toString();
var compiledTemplate = handlebars.compile(rawTemplate);
var result = compiledTemplate(dataPassedToTemplate);
res.write(result);

Adding a Global Router Param with Express 4.11

I am trying to create a global param that will be used by my Router with Express 4.11. The general function of this is that I am using router.all('/*',function(){}); This method is creating a list of all my routes to dynamically create a navbar in a partial jade template. I can pass the list to the response but I want to not have to call this on every route method I create i.e
router.all('/*',function(res,req,next){
var links = [];
console.log('Being Called...');
for(var i=0; i< router.stack.length; i++) {
var route = router.stack[i];
if(route['route'].path !== '/*'
&& route['route'].path !=='/favicon.ico'){
var name = (route['route'].path !=='/')? route['route'].path.toLowerCase().replace('/','') : "Home";
var active = (route['route'].path === url.parse(res.originalUrl).pathname)? true: false;
links.push({
name: name,
path: route['route'].path,
active: active
});
}
}
if(links.length > 0) {
console.log(res.locals);
res.set('appLinks',links);
}
next();
});
/*GET home page*/
router.get('/', function(req, res, next) {
res.render('index', {title: 'Home', appLinks: req.appLinks});
});
I want that appLinks to be a global param that is used by all of my routes. How do I go about this? or am I over thinking it and should just add that small line of code?
Thank you for the help.
You shouldn't use router.all but instead place following before any router.get or .post or whatever
router.use(function(req,res,next){
var links = [];
//your code here
res.locals.appLinks = links;
next();
});
anything added to res.locals is available in views so there's no need to pass it to res.render(...)

Using (and reusing) multiple mongoose database connections on express.js

I'm looking for the easiest & performant way to make a multitenant express.js app for managing projects.
Reading several blogs and articles, I figured out that, for my application, would be nice to have a database per tenant architecture.
My first try has been to use subdomains to detect the tenant, and then map the subdomain to a mongodb database.
I came up with this express middlewares
var mongoose = require('mongoose');
var debug = require('debug')('app:middleware:mongooseInstance');
var conns [];
function mongooseInstance (req, res, next) {
var sub = req.sub = req.subdomains[0] || 'app';
// if the connection is cached on the array, reuse it
if (conns[sub]) {
debug('reusing connection', sub, '...');
req.db = conns[sub];
} else {
debug('creating new connection to', sub, '...');
conns[sub] = mongoose.createConnection('mongodb://localhost:27017/' + sub);
req.db = conns[sub];
}
next();
}
module.exports = mongooseInstance;
Then I register the models inside another middleware:
var fs = require('fs');
var debug = require('debug')('app:middleware:registerModels');
module.exports = registerModels;
var models = [];
var path = __dirname + '/../schemas';
function registerModels (req, res, next) {
if(models[req.sub]) {
debug('reusing models');
req.m = models[req.sub];
} else {
var instanceModels = [];
var schemas = fs.readdirSync(path);
debug('registering models');
schemas.forEach(function(schema) {
var model = schema.split('.').shift();
instanceModels[model] = req.db.model(model, require([path, schema].join('/')));
});
models[req.sub] = instanceModels;
req.m = models[req.sub];
}
next();
}
Then I can proceed normally as any other express.js app:
var express = require('express');
var app = express();
var mongooseInstance = require('./lib/middleware/mongooseInstance');
var registerModels = require('./lib/middleware/registerModels');
app.use(mongooseInstance);
app.use(registerModels);
app.get('/', function(req, res, next) {
req.m.Project.find({},function(err, pets) {
if(err) {
next(err);
}
res.json({ count: pets.length, data: pets });
});
});
app.get('/create', function (req, res) {
var p = new req.m.Project({ name: 'Collin', description: 'Sad' });
p.save(function(err, pet) {
res.json(pet);
});
});
app.listen(8000);
The app is working fine, I don't have more than this right now, and I'd like to get some feedback before I go on, so my questions would be:
Is this approach is efficient? Take into account that a lot will be happening here, multiple tenants, several users each, I plan to setup webhooks in order to trigger actions on each instance, emails, etc...
Are there any bottlenecks/pitfalls I'm missing? I'm trying to make this scalable from the start.
What about the model registering? I didn't found any other way to accomplish this.
Thanks!
Is this approach is efficient?
Are there any bottlenecks/pitfalls I'm missing?
This all seems generally correct to me
What about the model registering?
I agree with #narc88 that you don't need to register models in middleware.
For lack of a better term, I would use a factory pattern. This "factory function" would take in your sub-domain, or however you decide to detect tenants, and return a Models object. If a given middleware wants to use its available Models you just do
var Models = require(/* path to your Model factory */);
...
// later on inside a route, or wherever
var models = Models(req.sub/* or req.tenant ?? */);
models.Project.find(...);
For an example "factory", excuse the copy/paste
var mongoose = require('mongoose');
var fs = require('fs');
var debug = require('debug')('app:middleware:registerModels');
var models = [];
var conns = [];
var path = __dirname + '/../schemas';
function factory(tenant) {
// if the connection is cached on the array, reuse it
if (conns[tenant]) {
debug('reusing connection', tenant, '...');
} else {
debug('creating new connection to', tenant, '...');
conns[tenant] = mongoose.createConnection('mongodb://localhost:27017/' + tenant);
}
if(models[tenant]) {
debug('reusing models');
} else {
var instanceModels = [];
var schemas = fs.readdirSync(path);
debug('registering models');
schemas.forEach(function(schema) {
var model = schema.split('.').shift();
instanceModels[model] = conns[tenant].model(model, require([path, schema].join('/')));
});
models[tenant] = instanceModels;
}
return models[tenant];
}
module.exports = factory;
Aside from potential (albeit probably small) performance gain, I think it also has the advantage of:
doesn't clutter up the request object as much
you don't have to worry as much about middleware ordering
allows more easily abstracting permissions for a given set of models, i.e. the models aren't sitting on the request for all middleware to see
This approach doesn't tie your models to http requests, so you might have flexibility to use the same factory in a job queue, or whatever.

Routing in Express.js - dynamic number of parameters

I've a concept for create routing with multiple parameters when numbers of it are dynamic, for example:
/v2/ModuleName/Method/Method2/
In Express I want parse it as: Modules.v2.ModuleName.Method.Method2(). When will be just one method, this should be of course Modules.v2.ModuleName.Method(). It's possible to do that?
You can split pathname then lookup the method from your Modules object like this:
// fields = ['v2', 'ModuleName', 'Method', 'Method2']
var method = Modules;
fields.forEach(function (field) {
method = method[field];
})
// call method
console.log(method());
Full code:
var express = require('express'), url = require('url');
var app = express();
Modules = {
method: function () { return 'I am root'},
v2: {
method: function () { return 'I am v2';}
}
};
app.get('/callmethod/*', function (req, res) {
var path = url.parse(req.url).pathname;
// split and remove empty element;
path = path.split('/').filter(function (e) {
return e.length > 0;
});
// remove the first component 'callmethod'
path = path.slice(1);
// lookup method in Modules:
var method = Modules;
path.forEach(function (field) {
method = method[field];
})
console.log(method());
res.send(method());
});
app.listen(3000);
Test on browser:
http://example.com:3000/callmethod/method
"I am root"
http://example.com:3000/callmethod/v2/method
"I am v2"
PS: you can improve this app to support pass params to a method via url:
http://example.com:3000/callmethod/v2/method?param1=hello&param2=word

Resources