86

Ok, so I have built a blog using Jekyll and you can define variables in a file _config.yml which are accessible in all of the templates/layouts. I am currently using Node.JS / Express with EJS templates and ejs-locals (for partials/layouts. I am looking to do something similar to the global variables like site.title that are found in _config.yml if anyone is familiar with Jekyll. I have variables like the site's title, (rather than page title), author/company name, which stay the same on all of my pages.

Here is an example of what I am currently doing.:

exports.index = function(req, res){
    res.render('index', { 
        siteTitle: 'My Website Title',
        pageTitle: 'The Root Splash Page',
        author: 'Cory Gross',
        description: 'My app description',
        indexSpecificData: someData
    });
};

exports.home = function (req, res) {
    res.render('home', {
        siteTitle: 'My Website Title',
        pageTitle: 'The Home Page',
        author: 'Cory Gross',
        description: 'My app description',
        homeSpecificData: someOtherData
    });
};

I would like to be able to define variables like my site's title, description, author, etc in one place and have them accessible in my layouts/templates through EJS without having to pass them as options to each call to res.render. Is there a way to do this and still allow me to pass in other variables specific to each page?

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
Cory Gross
  • 36,833
  • 17
  • 68
  • 80

8 Answers8

116

Update for Express Versions 4/5+:

The best solution for handling this is still by using app.locals as documented in the Express API Reference. As some of the comments here note, the API was updated in Express 4 such that app.locals is no longer a function, but is now simply an object. So if you try to call it as a function you will get an error.

To use app.locals in Express 4/5+ to accomplish the same thing as in my original example for Express 3 below, you could set app.locals like so anywhere in your application where you have access to the Express app object:

app.locals = {
    site: {
        title: 'ExpressBootstrapEJS',
        description: 'A boilerplate for a simple web application with a Node.JS and Express backend, with an EJS template with using Twitter Bootstrap.'
    },
    author: {
        name: 'Cory Gross',
        contact: 'coryg89@gmail.com'
    }
};

This would make all of these values globally available within all views accessible as site.title, site.description, author.name, and author.contact respectively.

NOTE: Alternatively, you can also set values on app.locals separately.

For example, this would also work:

app.locals.authorName = 'Cory Gross';
app.locals.authorContact = 'coryg89@gmail.com';

However, by default, this would not work, and would throw an error:

app.locals.author.name = 'Cory Gross';
app.locals.author.contact = 'coryg89@gmail.com';

This happens because, while app.locals already exists as an object exposed by Express which you can define properties on, app.locals.author does not yet exist and so the above will throw an error complaining that you trying to set a property on an object that is undefined. In order to do the above you would first have to set app.locals.author = {} as an empty object or just set everything under app.locals or app.locals.author at the same time, similar to my first example above.

Usage in Middleware

Although, the values set on app.locals are made globally accessible in all views, one of the potential issues with using it in Express 3 was that those values were not necessarily accessible within middleware functions by default. Because of this limitation, one alternative, as originally suggested in Pickel's answer, was setting the values on res.locals in a top level middleware instead so that the values would be available in all lower middleware as well as in all views. However, this alternative came with the downside that the logic for setting all of these values would be executed over and over again for every single request/response rather than being set one time and persisting as long as the server is running as is the case with app.locals.

In Express 4/5+, there is now a better solution if you want the values that you're setting on app.locals to be available within middleware functions as well as in all views. To solve this issue, the Express 4/5+ API now exposes the Express app object within all middleware functions, accessible as a property on the req parameter as req.app. So, by default, you can now easily access all of the global application scoped values set on app.locals within all middleware functions as well simply by accessing the object as req.app.locals within your middleware.

Original Answer for Express Version 3:

After having a chance to study the Express 3 API Reference a bit more I discovered what I was looking for. Specifically the entries for app.locals and then a bit farther down res.locals held the answers I needed.

I discovered for myself that the function app.locals takes an object and stores all of its properties as global variables scoped to the application. These globals are passed as local variables to each view. The function res.locals, however, is scoped to the request and thus, response local variables are accessible only to the view(s) rendered during that particular request/response.

So for my case in my app.js what I did was add:

app.locals({
    site: {
        title: 'ExpressBootstrapEJS',
        description: 'A boilerplate for a simple web application with a Node.JS and Express backend, with an EJS template with using Twitter Bootstrap.'
    },
    author: {
        name: 'Cory Gross',
        contact: 'coryg89@gmail.com'
    }
});

Then all of these variables are accessible in my views as site.title, site.description, author.name, author.contact.

I could also define local variables for each response to a request with res.locals, or simply pass variables like the page's title in as the optionsparameter in the render call.

EDIT: This method will not allow you to use these locals in your middleware. I actually did run into this as Pickels suggests in the comment below. In this case you will need to create a middleware function as such in his alternative (and appreciated) answer. Your middleware function will need to add them to res.locals for each response and then call next. This middleware function will need to be placed above any other middleware which needs to use these locals.

EDIT: Another difference between declaring locals via app.locals and res.locals is that with app.locals the variables are set a single time and persist throughout the life of the application. When you set locals with res.locals in your middleware, these are set everytime you get a request. You should basically prefer setting globals via app.locals unless the value depends on the request req variable passed into the middleware. If the value doesn't change then it will be more efficient for it to be set just once in app.locals.

Cory Gross
  • 36,833
  • 17
  • 68
  • 80
  • 1
    app.locals makes more sense than what I suggested. It has one catch though, you can't access the locals in your middleware. In this case it probably doesn't matter but it's one of those gotchas that you sometimes run into. – Pickels May 12 '13 at 16:31
  • You can also define configuration in a js or json file and simply require() it everywhere you need it. The file will only load once during the application, and every module that requires it gets access to the values it defined. See the various answers at http://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files – Joe Lapp Jul 18 '14 at 01:50
  • 54
    In **Express 4** `locals` is not a function, but an object. To set a property: `app.locals.site.title = 'Example';` – Martti Laine Jan 09 '15 at 22:39
  • Wow! Now whole app is configurable through a single file. A clean shot. +1 – Dipak May 07 '16 at 05:42
  • @MarttiLaine Thanks buddy! – Ali Emre Çakmakoğlu Jul 07 '16 at 14:57
  • Local variables are available in middleware on the request object in 4x via [req.app.locals](https://expressjs.com/en/4x/api.html#app.locals) – danialk May 26 '19 at 18:53
  • 1
    @MarttiLaine I can only seem to set one level down... so `app.locals.title = 'Example'` works but `app.locals.site.title = 'Example'` does not. Why is that? – volume one Nov 06 '19 at 12:59
  • @volumeone same here on express 4.17.1 – Remo Harsono Apr 28 '22 at 06:39
  • On Express 4 simply assign a object to app.locals like `app.locals = { site: { title: 'Example' } }` – campsjos Sep 29 '22 at 10:57
  • 1
    @volumeone @RemoHarsono This is because `app.locals` itself is already pre-defined by Express as an object which you can set properties on, and so directly setting `app.locals.title` works as expected—However, by default `app.locals.site` will be undefined initially, and so trying to directly set `app.locals.site.title` without first explicitly defining `app.locals.site` itself will throw an error complaining that you're trying to set a property on something that is undefined. See my updated answer above for more info. – Cory Gross Aug 03 '23 at 07:48
60

You can do this by adding them to the locals object in a general middleware.

app.use(function (req, res, next) {
   res.locals = {
     siteTitle: "My Website's Title",
     pageTitle: "The Home Page",
     author: "Cory Gross",
     description: "My app's description",
   };
   next();
});

Locals is also a function which will extend the locals object rather than overwriting it. So the following works as well

res.locals({
  siteTitle: "My Website's Title",
  pageTitle: "The Home Page",
  author: "Cory Gross",
  description: "My app's description",
});

Full example

var app = express();

var middleware = {

    render: function (view) {
        return function (req, res, next) {
            res.render(view);
        }
    },

    globalLocals: function (req, res, next) {
        res.locals({ 
            siteTitle: "My Website's Title",
            pageTitle: "The Root Splash Page",
            author: "Cory Gross",
            description: "My app's description",
        });
        next();
    },

    index: function (req, res, next) {
        res.locals({
            indexSpecificData: someData
        });
        next();
    }

};


app.use(middleware.globalLocals);
app.get('/', middleware.index, middleware.render('home'));
app.get('/products', middleware.products, middleware.render('products'));

I also added a generic render middleware. This way you don't have to add res.render to each route which means you have better code reuse. Once you go down the reusable middleware route you'll notice you will have lots of building blocks which will speed up development tremendously.

nem035
  • 34,790
  • 6
  • 87
  • 99
Pickels
  • 33,902
  • 26
  • 118
  • 178
  • I know this is an old answer, but the params in your `globalLocals` function for `req` and `res` are backwards. – brandon927 Mar 09 '15 at 07:21
  • Can I query data intended to be global, this way, e.g. navbar data that loads directly from db each page request? That seems to be this idea might be overstepping the usage of locals, e.g. a mongoose call. I am looking for direct load, on every route, since navbar is global, will look into caching later. Alternatively, maybe i need a function that runs once, then loads these to locals after collecting the data. – blamb Jul 02 '17 at 00:53
  • 1
    `res.locals` doesn't seem to be a function anymore. – killjoy May 14 '20 at 19:41
  • I use your approach to show some products into the footer. Thank you! – Carnaru Valentin Sep 15 '20 at 05:48
19

For Express 4.0 I found that using application level variables works a little differently & Cory's answer did not work for me.

From the docs: http://expressjs.com/en/api.html#app.locals

I found that you could declare a global variable for the app in

app.locals

e.g

app.locals.baseUrl = "http://www.google.com"

And then in your application you can access these variables & in your express middleware you can access them in the req object as

req.app.locals.baseUrl

e.g.

console.log(req.app.locals.baseUrl)
//prints out http://www.google.com
RamRovi
  • 986
  • 9
  • 13
17

In your app.js you need add something like this

global.myvar = 100;

Now, in all your files you want use this variable, you can just access it as myvar

Zach Saucier
  • 24,871
  • 12
  • 85
  • 147
Fernando Bustos
  • 349
  • 3
  • 6
  • I'm using require modules, and had to reference it as `global.myvar` everywhere, and that worked. This was a nice easy solution for socket.io, where I want to emit messages from handlers in other files. I put my "io" object on `global.myIO` and all is working great. – Thom Porter Nov 05 '13 at 11:58
  • 6
    This is actually not recommended or considered a good practice in Node development. Node.JS includes a module system implementation based on the CommonJS system and is completely centered on the idea of modules which do not share a global scope, instead using the require method. This also sets a global js var in Node instead of a local for Express view/templates like the question asks for. – Cory Gross Nov 24 '13 at 22:14
  • @CoryGross Is it a good practice to set request object as a global variable like this? – Ali Sherafat Jul 28 '17 at 10:33
  • Only this worked. Thanks Mr. F! @CoryGross how do I let all files in my application access a single object? – Divij Sehgal Jan 14 '19 at 20:50
  • This is the best answer. It means the global object is available to pass to a view or anywhere you want. Like `global.cdnURL = "https://cdn.domain.com` that you can pass to a view to load up static conent. – volume one Nov 21 '19 at 17:26
0

One way to do this by updating the app.locals variable for that app in app.js

Set via following

var app = express();
app.locals.appName = "DRC on FHIR";

Get / Access

app.listen(3000, function () {
    console.log('[' + app.locals.appName + '] => app listening on port 3001!');
});

Elaborating with a screenshot from @RamRovi example with slight enhancement.

enter image description here

Zach Saucier
  • 24,871
  • 12
  • 85
  • 147
Gajen Sunthara
  • 4,470
  • 37
  • 23
0

you can also use "global"

Example:

declare like this :

  app.use(function(req,res,next){
      global.site_url = req.headers.host;   // hostname = 'localhost:8080'
      next();
   });

Use like this: in any views or ejs file <% console.log(site_url); %>

in js files console.log(site_url);

Shaik Matheen
  • 1,233
  • 15
  • 14
0

With the differents answers, I implemented this code to use an external file JSON loaded in "app.locals"

Parameters

{
    "web": {
        "title" : "Le titre de ma Page",
        "cssFile" : "20200608_1018.css"
    }
}

Application

var express     = require('express');
var appli       = express();
var serveur     = require('http').Server(appli);

var myParams    = require('./include/my_params.json');
var myFonctions = require('./include/my_fonctions.js');

appli.locals = myParams;

EJS Page

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title><%= web.title %></title>
    <link rel="stylesheet" type="text/css" href="/css/<%= web.cssFile %>">
</head>

</body>
</html>

Hoping it will help

Juan
  • 690
  • 4
  • 15
-1

What I do in order to avoid having a polluted global scope is to create a script that I can include anywhere.

// my-script.js
const ActionsOverTime = require('@bigteam/node-aot').ActionsOverTime;
const config = require('../../config/config').actionsOverTime;
let aotInstance;

(function () {
  if (!aotInstance) {
    console.log('Create new aot instance');
    aotInstance = ActionsOverTime.createActionOverTimeEmitter(config);
  }
})();

exports = aotInstance;

Doing this will only create a new instance once and share that everywhere where the file is included. I am not sure if it is because the variable is cached or of it because of an internal reference mechanism for the application (that might include caching). Any comments on how node resolves this would be great.

Maybe also read this to get the gist on how require works: http://fredkschott.com/post/2014/06/require-and-the-module-system/

dewwwald
  • 287
  • 4
  • 14