Is it possible to create different routers using Express.Router that don't share middleware?
To me it seems that Express.Router uses a singleton, so no matter what I try, the middleware gets attached to all routers. So, without having to create multiple instances of the Express app, is there a way to achieve the following:
Create mutliple routers
var router_a = Express.Router();
var router_b = Express.Router();
Give each router unique routes and middleware
router_a.use(function(req, res, next){
console.log('Only works on router_a!');
});
router_a.get('/', function(req, res){
console.log('Only works on router_a!');
});
router_b.use(function(req, res, next){
console.log('Only works on router_b!');
});
router_b.get('/', function(req, res){
console.log('Only works on router_b!');
});
Attach each route to a custom url namespace
app.use('/a', router_a);
app.use('/b', router_b);
Is there a straight forward way to achieve this? After reading through the docs on the Router I don't see anything that suggests such is possible.
The one thing I see missing from your code is the call the next() in your middleware. If I add that to your code, it works perfectly fine for me.
The /b middleware is only called if the route starts with /b and same for the /a middleware with /a routes. And, to finish your code, you also have to send a response in your .get() handlers.
Here's the specific code I just tested:
var express = require('express');
var app = express();
var server = app.listen(80);
app.use(express.static('public'));
var router_a = express.Router();
var router_b = express.Router();
router_a.use(function(req, res, next){
console.log('.use() - Only works on router_a!');
next();
});
router_a.get('/', function(req, res){
console.log('.get() - Only works on router_a!');
res.send("router a, / route");
});
router_b.use(function(req, res, next){
console.log('.use() - Only works on router_b!');
next();
});
router_b.get('/', function(req, res){
console.log('.get() - Only works on router_b!');
res.send("router b, / route");
});
app.use('/a', router_a);
app.use('/b', router_b);
Related
I'm using node and express to create a rest api. I followed a tutorial where all the routes and its logic are saved in a routes.js file like this:
SERVER JS:
var express = require('express');
var app = express();
(...)
require('./app/routes.js')(app, port, express);
ROUTES.JS
module.exports = function(app, port, express) {
var apiRoutes = express.Router();
(...)
//Sample route
apiRoutes.get('/userfiles', function(req, res) {
UserFile.find({ owner: req.decoded.user.email }, function(err, filesList) {
if (err)
return done(err);
res.json({ success: true, files: filesList });
});
});
My problem is twofold:
1 - Routes can easily contain code thats 150 lines long, some of them far longer. It doesn't feel clean to have route declarations and the logic grouped together. Is it a good practice to do something like this instead?
apiRoutes.post('/randomRoute', function(req, res) {
return res.json(functionThatContainsTheActualCode(req));
});
(and then have an functionThatContainsTheActualCode function with all the logic in a different file).
2 - I have middleware that applies to some functions (for example, some routes are only accessible for logged in users and those routes go through an authentication middleware). Currently way I do it is declaring public routes before the middleware declaration and private routes after, which feels incredibly hacky. How can I separate public and private routes (and the middleware itself) in different files?
Problem 1:
We need to go deeper.
Change the route file to just require the actual router logic.
routes.js
// where app = express();
module.exports = (app) => {
// index.js happens to be a file exporting the router.
app.use('/', require('./index'));
// this is basically the idea. Create a separate file for the actual logic.
app.use('/route', require('.path/to/file'));
};
and in file.js
const express = require('express'),
router = express.Router();
router.verb('/path/', (req, res, next) => {
// do whatever
});
// this is required
module.exports = router;
Problem 2:
Middleware is basically a function taking in request, response, next as 3 params, doing something with the request and either sending out a response or moving on to the next middleware. That's why you need to call next if you want to move to next middleware in the chain.
Now all you need is a file that exports a function which takes request, response, next as params.
// lets call this auth.js
module.exports = function(req, res, next) {
// do logic
if () {
return res.send(); // or res.somethingThatSendsOutAHttpResponse()
}
// next middelware
next();
};
Since express routes are also middlewares, (mind blown), you can mount them top down.
To authenticate a route, just put the auth.js middleware on top of that route.
router.get('/', require('./auth'));
router.get('/', require('./log'));
router.get('/', (req, res, next) => {
// yolo
});
Now since this is web dev, you still got problems.
Now all your boring database queries are scattered everywhere.
Fear not, you can solve it, by, guess, creating another file.
apiRoutes.get('/userfiles', function(req, res) {
const userFile = require('/path/to/model/with/userfile/methods/exported/out');
// do something with userFile's methods
});
UPDATE 2: I just noticed that if on the app file I change the line:
app.use('/', home);
to:
app.use('/anything', home);
then all of the sub routes "roots" work fine.
UPDATE 3: I just realized something else. A method on the home.js file that I did not originally include below since I didn't think was relevant turns out to be the cause of the problem.
router.get('/:adventureId', (req, res) => {
var data;
//Irrelevant content that sets data as a JSON object.
res.json(data);
});
Turns out that every single sub route "root" is passing through here and since on the other routes adventureId is undefined then data is just an empty JSON object.
So the real question is: If this router bound to "/" and the other "roots" are bound to "/adventure" and "/test" why are all of them going through "/:adventrueId"?
I have a very simple ExpressJS application on which all of the "roots" in each and every single route other than home are not being handled and they always display an empty JSON object on the page.
In some posts it was mentioned that it could be a caching issue since these routes always return a 304 status, but I've tried doing an "Empty cach and hard reload" on Chrome and even with the 200 status I still get a blank page with an empty JSON object displayed on it. I tried it with MS Edge and got the exact same behavior.
Here's what I have:
On my app file
var app = express();
var home = require('./routes/home');
var adventure = require('./routes/adventure');
var test = require('./routes/test');
app.use('/', home);
app.use('/adventure', adventure);
app.use('/test', test);
On home.js file:
var express = require('express');
var router = express.Router();
router.get('/', (req, res) => {
console.log("This works fine with http://localhost:3000.");
res.render('home');
});
router.get('/:adventureId', (req, res) => {
var data;
//Irrelevant content that sets data as a JSON object.
res.json(data);
});
module.exports = router;
On the adventure.js file:
var express = require('express');
var router = express.Router();
router.use('/:id', (req, res) => {
console.log("This works fine with http://localhost:3000/adventure/5.");
next();
});
router.get('/:id', (req, res) => {
console.log("This works fine with http://localhost:3000/adventure/5.");
res.render('adventure');
});
//I've also tried putting this before the other routes and the result is the same.
router.get('/', (req, res) => {
console.log("This is never written in the console with http://localhost:3000/adventure.");
res.send("This is never rendered in the page.");
});
On the test.js file:
var express = require('express');
var router = express.Router();
router.use('/', (req, res) => {
console.log("This is never written on the console with http://localhost:3000/test.");
res.send("Hello from the test root route");
});
module.exports = router;
In the ExpressJS Router documentation and every single blog and example I've found it says that this is how it should work so I am really at a loss here.
Thank you.
If this router bound to "/" and the other "roots" are bound to "/adventure" and "/test" why are all of them going through "/:adventrueId"?
Because Express doesn't match routes based on which one matches best, it matches on which one matches first.
In your case, the route /:advertureId was declared before the routes for /adventure or /test. And /adventure and /test both match /:advertureId, so the handler for that route is called.
If you want to prevent this, declare more specific routes first:
app.use('/adventure', adventure);
app.use('/test', test);
app.use('/', home);
After much going around, several updates on the question and banging my head against the keyboard I finally understood what the problem is:
By having the routers set up like this:
app.use('/', home);
app.use('/adventure', adventure);
app.use('/game', gameService);
app.use('/test', test);
and on the "home" router having this method signature:
router.get('/:adventureId', (req, res) => {
var data;
//Irrelevant content that sets data as a JSON object.
res.json(data);
});
Every single sub route "root" was being interpreted as the parameter of the previous method so:
/adventure
/test
/anything
would be handled as:
adventrueId = adventrue
adventureId = test
adventrueId = anything
The solution was to do this:
app.use('/adventure', adventure);
app.use('/game', gameService);
app.use('/test', test);
app.use('/', home);
And now everything works.
So basically I want my code being able to go mywebsite.com/username and itll take them to their profile.
My app.js has -
var user = require('./routes/user');
app.use('/*', user);
and then in my user.js I have
router.get('/:username', function(req, res) {
console.log('the user name', req.params.username);
})
If I change router.get to just router.get('/') my console.log logs out undefined and if I leave it as router.get('/:username') I get a 404.. I also tried doing app.use('/:username', user) as well but that still didn't work.
You should't need to have a wildcard like you do on app.use
Try something like this.
Also see the express router documentation http://expressjs.com/en/guide/routing.html
// Required Moduiles
var express = require('express');
var router = express.Router();
var app = express();
// Add route to get
router.get('/:username', function(req, res) {
res.status(200).send(req.params.username)
})
// Add username
app.use('/', router)
// Start express server
app.listen(3030);
Update -
Just using app.use(router) would do the same thing if all your routes are going to be contained in this new router instance / middleware you are creating in a separate file.
i'd like to nest resources like the below eg.
/// fileA.js
app.use('/dashboards/:dashboard_id/teams', teams);
[...]
///teams.js
router.get('/', function(req, res, next) {
[...]
}
but I can't get the req.params["dashboard_id"] parameter in teams because it seems it is already substituted with the parameter value.
I've tried to baypass the problem by calling an intermediate function and pass somewhere the parameter but I can't figure out where ...
Do you have an answer?
Thanks,
Franco
You may try this solution:
//declare a function that will pass primary router's params to the request
var passPrimaryParams = function(req, res, next) {
req.primaryParams = req.params;
next();
}
/// fileA.js
app.use('/dashboards/:dashboard_id/teams', passPrimaryParams);
app.use('/dashboards/:dashboard_id/teams', teams);
///teams.js
router.get('/', function(req, res, next) {
var dashboardId = req.primaryParams['dashboard_id']; //should work now
//here you may also use req.params -- current router's params
}
Using Express 4.0.0 or above at the time of this writing:
To make the router understand nested resources with variables you will need create and bind a new router using app.use for every base path.
//creates a new router
var dashboardRouter = express.router();
//bind your route
dashboardRouter.get("/:dashboard_id/teams", teams);
//bind to application router
app.use('/dashboards', dashboardRouter);
This way Express will see the first part of the path and go to the /dashboards route, which has the :dashboard_id/teams path.
You can use the mergeParams option here
// fileA.js
app.use('/dashboards/:dashboard_id/teams', teams);
// teams.js
const router = express.Router({ mergeParams: true })
router.get('/', function(req, res, next) {
// you will have access to req.params.dashboard_id here
}
You can also see this answer: Rest with Express.js nested router
I am building an app with express js which will have different clients like web and mobile. I didnt want to use one app for both as some middleware would be additional burden. For say like session middleware. So is it possible for one project to have two apps. And how would it work?
The app object that you make in express is a function(req,res,next) that is suitable for Express's own middleware chains. So you can use app.use to send requests matching a leading path fragment to an app defined elsewhere.
Docs: http://expressjs.com/api.html#app.use
$ npm install express
//mobile.js
var app = require('express')();
app.get('/', function(req, res){
res.send('Mobile Route')
});
module.exports = app;
//desktopApp.js
var http = require('http');
var express = require('express');
var desktopApp = express();
var mobileApp = require('./mobile.js');
desktopApp.use('/mobile', mobileApp)
desktopApp.use(desktopApp.router);
desktopApp.use(express.errorHandler());
desktopApp.get('/', function(req, res){
res.send('Desktop Route')
});
desktopApp.get('/mobile', function(req, res){
// Because Express respects the order that you set up the middleware chain,
// the mobileApp `/mobile` route gets first dibs to send a response or next()
res.send('Inaccessible Desktop Route')
});
desktopApp.get('/mobile/foobar', function(req, res){
// When mobileApp can't find any suitable route matching this path, it gives
// up, and desktopApp continues to pass the request down the middleware stack.
// It ends up matching this route, where we send a response
res.send('Desktop Route')
});
http.createServer(desktopApp).listen(3000, function(){
console.log('Listening on 3000');
});
// Results
$ curl localhost:3000/
Desktop Route
$ curl localhost:3000/mobile/
Mobile Route
See the vhost example on the express github repository.
You can have a "main" app, which routes the requests to one app or another. You should write a middleware to establish the conditions where one app or another are requested. express.vhost is a good example, but maybe you need other checks than the domain one.
main-app.js
(The file called to start the server.)
// load dependencies
var main = express();
main.use( express.vhost( 'mobile', require( './the-mobile-app' ) );
main.use( express.vhost( '*', require( './the-web-app' ) );
main.listen( /*...*/ )
the-mobile-app and the-web-app.js
var app = express();
//
// setup your application conf, middleware, and routes
//
module.exports = app;
I wanted to share a different approach that I used in a project recently:
function renderAppropriate(template1, template2){
return function(req, res){
if(req.session && req.session.mobileOn){
res.render(template1);
} else {
res.render(template2);
}
};
};
app.get('/', function(req, res, next){
// do some stuff
next()
}, renderAppropriate('someMobileTemplate', 'someDesktopTemplate')
);