In nodejs & express, what's the difference between settings & locals - node.js

Starting my way in node + express, what's the difference between:
app.set(key, value)
and
app.locals({key: value});
I've read the express docs and it states that app.locals are passed to all the rendered views, but I was also able to access the settings from a jade view as well (using #{settings.someKey}).
As both are available in jade templates I can't seem to figure out what the difference or different usage for the 2.

The difference is that by manipulating app.locals directly, you can create 'top level' variables for your templates, instead of having to use the settings. prefix.
app.set(key, value) is the same as app.locals.settings[key] = value; the former is the preferred way of configuring certain parts of Express (like setting view engine).
EDIT: small demo to show how they do the same:
var app = require('express')();
app.set('foo', 'bar');
console.log('app.get("foo"):', app.get('foo')); // 'bar'
console.log('app.locals.settings.foo:', app.locals.settings.foo); // 'bar'
app.locals.settings['foo'] = 'another bar';
console.log('2nd app.get("foo"):', app.get('foo')); // 'another bar'

Related

Application-scope Variables in Node.js?

Is there a variable scope in Node.js that persists longer than the request, that is available to all application users? I'd like to create a variable that can be read/written to by multiple app users, like so:
var appScope.appHitCount += 1;
The session scope won't work because it is user specific. Looking for application specific. I couldn't find anything in the Node.js docs. Thanks.
If you have a variable scoped to the app module it will be available and shared by everything within the scope. I imagine in a big app you would need to be careful with this, but it's just regular javascript scoping. For example this will continue to append to the array as you hit it:
const express = require('express')
const app = express()
var input = []
app.get('/:input/', (req, res) => {
var params = req.params
input.push(params.input)
res.send("last input was: " + JSON.stringify(input))
})
app.listen(8080, () => console.log('Example app listening on port 8080!'))
Now visiting 'http://localhost:8080/hi' returns:
last input was: ["hi"]
and then 'http://localhost:8080/there' returns:
last input was: ["hi", "there"]
...etc.
If you want something shared by the entire app (i.e. all the modules) you could set up a module that is require()d by all the modules and i=has the responsibility of getting and setting that value.

Restify set method like in Express

In Express 4.0, after declaring the server I do the following to set a server-wide variable...
var app = express();
app.set('foo', 'bar');
I don't see a method like that in Restify's documentation, so I'm just declaring an object inside the server that holds my variables.
Is that correct? Is there a better way to do this in Restify?
It sounds like that would work, but why not just create a module to hold your variables? Create a file named vars.js somewhere appropriate and make it like this:
module.exports = {
my_var: 2112,
other_var: 'signals'
}
Then wherever you need access to those variable just
var all_vars = require('./path/to/var.js');
and you'll have them.

How can I bootstrap models from express js into backbone at page load?

I have some data in a mongodb database and want to pass it to a backbone collection when I load the home page. One way of doing this would be to set up a node route like this:
exports.index = function(req, res){
db.users.find(function(err, docs) {
var docs_string = JSON.stringify(docs);
res.send(docs_string);
};
};
But this won't work because it won't render my jade template that pulls in the backbone code, it simply shows the JSON in plain text.
Alternatively, I could render my jade template passing in the data as a variable to jade:
exports.index = function(req, res){
db.users.find(function(err, docs) {
var docs_string = JSON.stringify(docs);
res.render('index', {
title: "Data",
docs_string: docs_string
})
});
};
Then in the jade template, have a script like this to add the users to my user collection:
script
var docs = !{docs_string};
var users = new app.Users();
_.each(docs, function(doc) {
var user = new app.User(doc);
users.add(user);
})
But this seems wrong, since I don't really want to pass the data to the jade template, I want to pass it to a backbone collection. Also, with this solution I don't know how to then include an underscore template (on the backbone side of things) into the page rendered by jade on the server side.
What is the standard way of passing data from a node server to a backbone collection?
Assuming your data is an object, you should convert it to string using JSON.stringify() and then insert in a page inside script tag, so your resulting HTML looks like this (I don't use Jade):
<script>
var data = {...}; // in template instead of {...} here should be the instruction to insert your json string
</script>
Then when the page loads, your script will be executed and the data will be available as a global variable in the browser so you can initialise backbone collection using it. This all is a good idea only to bootstrap your data on the first page load (to avoid extra request) and then use API to request data for this and other pages.
Check out Steamer, a tiny node / express module made for this exact purpose.
https://github.com/rotundasoftware/steamer

Multi-language routes in express.js?

I'm wondering if there is a best practise example on how to implement multi-lanuage routes in express.js. i want to use the accept-language header to get the browser language and then redirect automatically to the corresponding language route like
www.foo.bar/de/startseite OR
www.foo.bar/en/home
Any advice on this?
i have done the following:
install i18n-node modul and register in the express js. here is code.
var express = require('express')
, routes = require('./routes')
, http = require('http')
, i18n = require("i18n");
var app = express();
i18n.configure({
// setup some locales - other locales default to en silently
locales:['de', 'en'],
// disable locale file updates
updateFiles: false
});
app.configure(function(){
...
app.use(i18n.init);
...
});
// register helpers for use in templates
app.locals({
__i: i18n.__,
__n: i18n.__n
});
after this set the following to get all request
// invoked before each action
app.all('*', function(req, res, next) {
// set locale
var rxLocal = /^\/(de|en)/i;
if(rxLocal.test(req.url)){
var arr = rxLocal.exec(req.url);
var local=arr[1];
i18n.setLocale(local);
} else {
i18n.setLocale('de');
}
// add extra logic
next();
});
app.get(/\/(de|en)\/login/i, routes.login);
maybe this help.
I'd just serve up the content in the detected language directly.
For example, example.com/home serves up the home page in the best available Accept-Language (possibly overridden by cookie if you provide a language selection option on the site itself).
You'd want to make sure that your response's Vary: header includes Accept-Language.
IMO, including language codes in the URI is an ugly hack. The RFC's intent is that a single resource (your home page) is universally represented by a single URI. The entity returned for a URI can vary based on other information, such as language preferences.
Consider what happens when a German-speaking user copies a URL and sends it to an English-speaking user. That recipient would prefer to see your site in English, but because he has received a link that points to example.com/de/startseite, he goes straight to the German version.
Obviously, this isn't ideal for full internationalization of what the user sees in the address bar (since home is English), but it's more in line with the RFCs' intent, and I'd argue it works better for users, especially as links get spread around email/social/whatever.
Middleware recommendation
The answer by #miro is very good but can be improved as in the following middleware in a separate file (as #ebohlman suggests).
The middleware
module.exports = {
configure: function(app, i18n, config) {
app.locals.i18n = config;
i18n.configure(config);
},
init: function(req, res, next) {
var rxLocale = /^\/(\w\w)/i;
if (rxLocale.test(req.url)){
var locale = rxLocale.exec(req.url)[1];
if (req.app.locals.i18n.locales.indexOf(locale) >= 0)
req.setLocale(locale);
}
//else // no need to set the already default
next();
},
url: function(app, url) {
var locales = app.locals.i18n.locales;
var urls = [];
for (var i = 0; i < locales.length; i++)
urls[i] = '/' + locales[i] + url;
urls[i] = url;
return urls;
}
};
Also in sample project in github.
Explanation
The middleware has three functions. The first is a small helper that configures i18n-node and also saves the settings in app.locals (haven't figured out how to access the settings from i18n-node itself).
The main one is the second, which takes the locale from the url and sets it in the request object.
The last one is a helper which, for a given url, returns an array with all possible locales. Eg calling it with '/about' we would get ['/en/about', ..., '/about'].
How to use
In app.js:
// include
var i18n = require('i18n');
var services = require('./services');
// configure
services.i18nUrls.configure(app, i18n, {
locales: ['el', 'en'],
defaultLocale: 'el'
});
// add middleware after static
app.use(services.i18nUrls.init);
// router
app.use(services.i18nUrls.url(app, '/'), routes);
Github link
The locale can be accessed from eg any controller with i18n-node's req.getLocale().
RFC
What #josh3736 recommends is surely compliant with RFC etc. Nevertheless, this is a quite common requirement for many i18n web sites and apps, and even Google respects same resources localised and served under different urls (can verify this in webmaster tools). What I would recommended though is to have the same alias after the lang code, eg /en/home, /de/home etc.
Not sure how you plan on organizing or sharing content but you can use regular expressions with express routes and then server up different templates. Something like this:
app.get(/^\/(startseite|home)$/, function(req, res){
});
One thing that I did was to organize my content with subdomains and then use middleware to grab the content out of the database based splitting the url, but they all shared the same routes and templates.
Write a middleware function that parses any "Accept-Language" headers and sets a request-level local variable to an appropriate code (like a two-letter language code) with a default value (like "en") if there are no such headers or you don't support any language listed. In your routes, retrieve the local and tack it on to any template file names, and branch on it if there's any language-dependent processing other than template selection.

In Express.js, how can I render a Jade partial-view without a "response" object?

Using Express.js, I'd like to render a partial-view from a Jade template to a variable.
Usually, you render a partial-view directly to the response object:
response.partial('templatePath', {a:1, b:2, c:3})
However, since I'm inside a Socket.io server event, I don't have the "response" object.
Is there an elegant way to render a Jade partial-view to a variable without using the response object?
Here's the straight solution to this problem for express 3 users (which should be widely spread now):
res.partial() has been removed but you can always use app.render() using the callback function, if the response object is not part of the current context like in Liors case:
app.render('templatePath', {
a: 1,
b: 2,
c: 3
},function(err,html) {
console.log('html',html);
// your handling of the rendered html output goes here
});
Since app.render() is a function of the express app object it's naturally aware of the configured template engine and other settings. It behaves the same way as the specific res.render() on app.get() or other express request events.
See also:
http://expressjs.com/api.html#app.render for app.render()
https://github.com/visionmedia/express/wiki/Migrating-from-2.x-to-3.x for express 2.x > 3.x migration purposes
You can manually compile the Jade template.
var jade = require('jade');
var template = require('fs').readFileSync(pathToTemplate, 'utf8');
var jadeFn = jade.compile(template, { filename: pathToTemplate, pretty: true });
var renderedTemplate = jadeFn({data: 1, hello: 'world'});

Resources