113

I want to split up my routes into different files, where one file contains all routes and the other one the corresponding actions. I currently have a solution to achieve this, however I need to make the app-instance global to be able to access it in the actions. My current setup looks like this:

app.js:

var express   = require('express');
var app       = express.createServer();
var routes    = require('./routes');

var controllers = require('./controllers');
routes.setup(app, controllers);

app.listen(3000, function() {
  console.log('Application is listening on port 3000');
});

routes.js:

exports.setup = function(app, controllers) {

  app.get('/', controllers.index);
  app.get('/posts', controllers.posts.index);
  app.get('/posts/:post', controllers.posts.show);
  // etc.

};

controllers/index.js:

exports.posts = require('./posts');

exports.index = function(req, res) {
  // code
};

controllers/posts.js:

exports.index = function(req, res) {
  // code
};

exports.show = function(req, res) {
  // code
};

However, this setup has a big issue: I have a database- and an app-instance I need to pass to the actions (controllers/*.js). The only option I could think of, is making both variables global which isn't really a solution. I want to separate routes from the actions because I have a lot of routes and want them in a central place.

What's the best way to pass variables to the actions but separate the actions from the routes?

Claudio Albertin
  • 2,076
  • 4
  • 18
  • 20
  • How does your controllers.js look like? Maybe you can make it a function (instead of an object) that can receive parameters. – mihai Apr 10 '12 at 15:24
  • require('controllers') requires controllers/index.js. However, a function won't work because I use the object in the routes (see routes.js) and thus can't pass arguments to it, even if it's a function. – Claudio Albertin Apr 10 '12 at 15:42

9 Answers9

177

Use req.app, req.app.get('somekey')

The application variable created by calling express() is set on the request and response objects.

See: https://github.com/visionmedia/express/blob/76147c78a15904d4e4e469095a29d1bec9775ab6/lib/express.js#L34-L35

Johann
  • 4,107
  • 3
  • 40
  • 39
Feng
  • 2,872
  • 3
  • 23
  • 20
  • Thanks. I think this is the best way of accessing variables set with app.set('name', val); – Pavel Kostenko Jul 16 '13 at 18:07
  • 4
    Don't forget to call `app.set('somekey', {})` in app.js – ankitjaininfo Aug 08 '14 at 13:26
  • 3
    My only gripe about this way though I do love it is that when you are trying to run an app.locals.authorized as such (not in main.js): `app.route('/something').get(app.locals.authorized,function(req,res,next){});` is not possible because it's outside the req scope. – gabeio Jan 06 '15 at 04:16
  • I am using different passport strategy for different query parameter. So I am trying to set passport.use("strategy-name") in a middleware. Even if I store passport in that middleware only with let passport = req.app,get('passport'). It is being modified for another set of requests. Why is it so ? – Kartikeya Mishra Jul 01 '20 at 20:33
  • If i do this, then req object will have addition Object instances like redis and db in my case. Won't it effect application performance ? eg: in index.js app.set('redis',redis_client); in routes/example.js router = require('express').Router(); route.get('/test',(req,res,next)=>{ conosle.log(req.app.get('redis')); return res.send("//done"); }) – Tekraj Shrestha Jul 12 '20 at 17:14
105

Node.js supports circular dependencies.
Making use of circular dependencies instead of require('./routes')(app) cleans up a lot of code and makes each module less interdependent on its loading file:


app.js
var app = module.exports = express(); //now app.js can be required to bring app into any file

//some app/middleware setup, etc, including 
app.use(app.router);

require('./routes'); //module.exports must be defined before this line


routes/index.js
var app = require('../app');

app.get('/', function(req, res, next) {
  res.render('index');
});

//require in some other route files...each of which requires app independently
require('./user');
require('./blog');


-----04/2014 update-----
Express 4.0 fixed the usecase for defining routes by adding an express.router() method!
documentation - http://expressjs.com/4x/api.html#router

Example from their new generator:
Writing the route:
https://github.com/expressjs/generator/blob/master/templates/js/routes/index.js
Adding/namespacing it to the app: https://github.com/expressjs/generator/blob/master/templates/js/app.js#L24

There are still usecases for accessing app from other resources, so circular dependencies are still a valid solution.

royhowie
  • 11,075
  • 14
  • 50
  • 67
Will Stern
  • 17,181
  • 5
  • 36
  • 22
  • 1
    "less interdependent on it's loading file" - it's dependent on the specific *filepath* of its loading file. That's very tight coupling, so let's not pretend it's not. – Camilo Martin Sep 05 '14 at 11:49
  • 3
    Just be very careful (read: don't do what I've been struggling with for the past hour+) that in `app.js` you require the routing file _after_ exporting the app. Circular `require()` calls can make for a real mess, so be sure you know [how they work](http://nodejs.org/docs/latest/api/modules.html#modules_cycles)! – Nateowami Feb 22 '15 at 13:20
  • I honestly think that the answer from @Feng about using req.app.get('somekey') is indeed a way better and cleaner solution than using circulr dependencies. – Claudio Mezzasalma Feb 01 '16 at 15:39
  • @Green if app is empty, you required a file that required `app` BEFORE app `module.exports` was defined. You have to instantiate `app`, set `module.exports`, then require files that might require `app` But either way, doing the circular dependencies is an anti-pattern that express has solved - you shouldn't need to do that anymore. – Will Stern Jun 05 '17 at 20:44
26

Like I said in the comments, you can use a function as module.exports. A function is also an object, so you don't have to change your syntax.

app.js

var controllers = require('./controllers')({app: app});

controllers.js

module.exports = function(params)
{
    return require('controllers/index')(params);
}

controllers/index.js

function controllers(params)
{
  var app = params.app;

  controllers.posts = require('./posts');

  controllers.index = function(req, res) {
    // code
  };
}

module.exports = controllers;
mihai
  • 37,072
  • 9
  • 60
  • 86
  • Is it OK to return an object inside the function or is it better so set the methods the way you do in your example? – Claudio Albertin Apr 10 '12 at 16:13
  • i think either approach is ok. – mihai Apr 10 '12 at 16:18
  • Because I have a lot of methods, I'd prefer to set them as an object instead of each manually. This would work when I just return the object, but isn't there a solution which is a bit flatter? My actual methods would be indented twice... – Claudio Albertin Apr 10 '12 at 16:20
  • Not sure if I understood you, but I guess you could move the implementation outside that `controllers` function, something like: http://jsfiddle.net/mihaifm/yV79K/ – mihai Apr 10 '12 at 16:32
  • does not controllers/index.js need to return controllers var? – Yalamber Jan 28 '13 at 11:10
6

Or just do that:

var app = req.app

inside the Middleware you are using for these routes. Like that:

router.use( (req,res,next) => {
    app = req.app;
    next();
});
asanchez
  • 454
  • 4
  • 13
  • 1
    Someone tell me why this is not the accepted answer? For dependencies you use `app.use('my-service', serviceInstance)` in the main router and `req.app.get('my-service')` in the controller as mentioned by @Feng – Felipe Feb 24 '19 at 23:58
  • if you use `var app = req.app` in `myMiddlewareFile.js` and 'update it' with something like `app.locals.mongodb_client = mongdb_client`, do you need to export `app` from that file to make that property accessible to the rest of the middleware files? or is `app` updated 'globally' from that assignment? – user1063287 Nov 27 '21 at 10:17
1
  1. To make your db object accessible to all controllers without passing it everywhere: make an application-level middleware which attachs the db object to every req object, then you can access it within in every controller.
// app.js
let db = ...;  // your db object initialized
const contextMiddleware = (req, res, next) => {
  req.db=db;
  next();
};
app.use(contextMiddleware);
  1. to avoid passing app instance everywhere, instead, passing routes to where the app is
// routes.js  It's just a mapping.
exports.routes = [
  ['/', controllers.index],
  ['/posts', controllers.posts.index],
  ['/posts/:post', controllers.posts.show]
];

// app.js
var { routes }    = require('./routes');
routes.forEach(route => app.get(...route));
// You can customize this according to your own needs, like adding post request

The final app.js:

// app.js
var express   = require('express');
var app       = express.createServer();

let db = ...;  // your db object initialized
const contextMiddleware = (req, res, next) => {
  req.db=db;
  next();
};
app.use(contextMiddleware);

var { routes }    = require('./routes');
routes.forEach(route => app.get(...route));

app.listen(3000, function() {
  console.log('Application is listening on port 3000');
});

Another version: you can customize this according to your own needs, like adding post request

// routes.js  It's just a mapping.
let get = ({path, callback}) => ({app})=>{
  app.get(path, callback);
}
let post = ({path, callback}) => ({app})=>{
  app.post(path, callback);
}
let someFn = ({path, callback}) => ({app})=>{
  // ...custom logic
  app.get(path, callback);
}
exports.routes = [
  get({path: '/', callback: controllers.index}),
  post({path: '/posts', callback: controllers.posts.index}),
  someFn({path: '/posts/:post', callback: controllers.posts.show}),
];

// app.js
var { routes }    = require('./routes');
routes.forEach(route => route({app}));
Iceberg
  • 2,744
  • 19
  • 19
0

Let's say that you have a folder named "contollers".

In your app.js you can put this code:

console.log("Loading controllers....");
var controllers = {};

var controllers_path = process.cwd() + '/controllers'

fs.readdirSync(controllers_path).forEach(function (file) {
    if (file.indexOf('.js') != -1) {
        controllers[file.split('.')[0]] = require(controllers_path + '/' + file)
    }
});

console.log("Controllers loaded..............[ok]");

... and ...

router.get('/ping', controllers.ping.pinging);

in your controllers forlder you will have the file "ping.js" with this code:

exports.pinging = function(req, res, next){
    console.log("ping ...");
}

And this is it....

0

If you want to pass an app-instance to others in Node-Typescript :

Option 1: With the help of import (when importing)

//routes.ts
import { Application } from "express";
import { categoryRoute } from './routes/admin/category.route'
import { courseRoute } from './routes/admin/course.route';

const routing = (app: Application) => {
    app.use('/api/admin/category', categoryRoute)
    app.use('/api/admin/course', courseRoute)
}
export { routing }

Then import it and pass app:

import express, { Application } from 'express';

const app: Application = express();
import('./routes').then(m => m.routing(app))

Option 2: With the help of class

// index.ts
import express, { Application } from 'express';
import { Routes } from './routes';


const app: Application = express();
const rotues = new Routes(app)
...

Here we will access the app in the constructor of Routes Class

// routes.ts
import { Application } from 'express'
import { categoryRoute } from '../routes/admin/category.route'
import { courseRoute } from '../routes/admin/course.route';

class Routes {
    constructor(private app: Application) {
        this.apply();
    }

    private apply(): void {
       this.app.use('/api/admin/category', categoryRoute)
       this.app.use('/api/admin/course', courseRoute)
    }
}

export { Routes }
Abolfazl Roshanzamir
  • 12,730
  • 5
  • 63
  • 79
0
var express = require('express');

var router = express.Router();

router.get('/', function(req, res, next) {
   console.log(req.app); // use req.app to get app instance ;

   res.render('index', { title: 'Express' });

});

module.exports = router;
wintzh
  • 11
  • 1
-1

For database separate out Data Access Service that will do all DB work with simple API and avoid shared state.

Separating routes.setup looks like overhead. I would prefer to place a configuration based routing instead. And configure routes in .json or with annotations.

Eldar Djafarov
  • 23,327
  • 2
  • 33
  • 27