Simulate File Structure in URL - node.js

I have content with a hierarchical structure which is stored in a database. I want to have the URL reflect the path to the current element. E.g. if I wanted to represent a database of animals, I might want a URL to be: animals.com/kingdom/phylum/class/order/family/genus which would show a list of species in that genus.
How can I accept requests to a URL matching that format, parse it, and show the appropriate page?

If you want to do it yourself rather than use a framework, you can use url.parse() to extract the path from a url, and then use split() to create an array from the path element.
var url = require('url');
var myUrl = "http://animals.com/kingdom/phylum/class/order/family/genus";
var myPath = url.parse(myUrl).path;
var elements = myPath.substr(1).split('/');
console.log(elements); // [ 'kingdom', 'phylum', 'class', 'order', 'family', 'genus' ]
You might consider using a framework like Express that offers robust routing options for this stuff. For example, in Express, you could do something like this:
app.get('/animal/:kingdom/:phylum/:class', function (req, res, next) {
console.log('Kingdom: ', req.params.kingdom);
console.log('Phylum: ', req.params.phylum);
console.log('Class: ', req.params.class);
res.send('Hello animal, lover!');
});

Related

How can we transfer id data with node index.js?id=1

How can we transfer id data with node?
node index.js?id=1
var express = require('express');
var app = express();
app.get('/:id', function(req, res) {
res.send('id: ' + req.params.id);
console.log(req.params.id);
});
In nodejs.org there's a piece of documentation where the functionality you desire is explained in a clear and simple way (https://nodejs.org/en/knowledge/HTTP/clients/how-to-access-query-string-parameters/).
Basically, what you are using in the code you showed returns the part of the path where you use the ":[name]" (a subdirectory).
What you actually want to get are the query string parameters by using url.parse(req.url,true).query which returns an object with a set of key:value pairs with the name of each parameter and respective value.
Be aware that to use that line of code you'll need at least to require the url module.
I hope I made myself clear and that my answer helps you!

Complex NodeJS / Express REGEX routing

I'm trying to create a NodeJS Express API (route) which has the following characteristics:
It has a base path, in my case it is /web/views. This part is a static value and doesn't change for as long as the server is up.
I can do this as follows:
const BASE = '/web/views'; // defined externally/elsewhere
app.get(BASE, function handleRequest(req, res) {
// handle API request...
}
Next, I expect to be provided with a resource. Given the name of this resource, I locate a file and send it to the client.
I can do this as follows:
app.get(BASE + '/:resource', function handleRequest(req, res) {
var resource = req.params.resource;
// handle API request...
}
So on the client, I invoke it this way:
GET /web/views/header
All of this works so far... but my problem is that my 'resource' can actually be a path in itself, such as:
GET /web/views/menu/dashboard
or a longer path, such as:
GET /web/views/some/long/path/to/my/xyz
I was using the following REGEX mapping:
const DEFAULT_REGEX = '/(\*/)?:resource';
or more precisely:
app.get(BASE + DEFAULT_REGEX, function handleRequest(req, res) {
var resource = req.params.resource;
// handle API request...
}
This works with an arbitrary length path between my BASE value and the :resource identifier, but the problem is that my resource variable only has
the xyz portion of the path and not the full path (ie: /some/long/path/to/my/xyz).
I could simply cheat and strip the leading BASE from the req.url, but I though there would be a REGEX rule for it.
If anyone knows how to do such advanced REGEX routing, I'd appreciate it.
Thanks!
Sure, so I think the easiest way is to simply not worry about using Regex, but instead just use a wildcard. You lose the cool params name, but otherwise it works as you're looking for. For example:
const express = require('express');
const app = express();
const port = 3000;
const BASE = '/web/views'
app.get(`${BASE}/*`, (req, res) => {
res.send(req.url);
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
If you hit http://localhost:3000/web/views/path/to/my/resource, in my example the response content will be /web/views/path/to/my/resource, so from there it's some simple string manipulation to pull the bit you want:
let resource = req.url.split('/web/views')[1];
// resource will equal /path/to/my/resource if the above URL is used
Of course you could get fancier with your string parsing to check for errors and such, but you get the idea.
You could even setup a middleware to get that resource piece for other handlers to work from:
app.use(`${BASE}/*`, (req, res, next) => {
const resource = req.url.split(BASE)[1];
req.resource = resource;
next();
});
Then all subsequent routes will have access to req.resource.

I have an API route and looking to make it more dynamic

My route is defined as
app.route('/api/*/*/*')
.get(function(req, res) {
var entity = req.params['0'];
var field = req.params['1'];
var params = req.params['2'];
})
Is there a way to make it more dynamic? Say I only want to send 2 parameters or just call /api, or even pass 4 parameters?
Is there a way to make my route definition more open to that without explicitly defining the number of * in the route?
You can do something like below:
app.get('/api/*', function(req, res) {
var paths = req.params[0].split('/');
//use paths
})
e.g for a url like '/api/a/b/c', paths will be ['a', 'b', 'c'].

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.

Resources