I am trying to make a middleware for handling url aliases, what I am doing right now is :
// [...]
module.exports = function() {
return function(req, res, next) {
// getAlias would get an object {alias:"alias/path",source:"/real/path"} or null
var alias = getAlias(req.url);
if(alias) {
req.url = alias.source;
}
next();
};
};
So basicaly I am looking in a store for the requested url and if it is found as an alias I change request.url to the source path to that alias so that express calls the right route.
The problem is request.url and request.path have the same value, but changing request.path does not work while request.url works. In addition I am not sure which one i have to test agains.
Things work when I interact with request.url but just wanted to make sure that I am doing it the proper way.
Any thoughts ?
Rewriting the req.url property is the correct way for internally rerouting requests. That is why there is a req.originalUrl for the cases where one does change the original URL.
This is what the Express documentation states for req.originalUrl:
This property is much like req.url, however it retains the original
request url, allowing you to rewrite req.url freely for internal
routing purposes.
The req.url property isn't documented, but from the statement above you can infer it's meant to be used in the way you explained. It is also used in that way in some of the Express tests.
You can use run-middleware module exactly for that. Just run the handler you want by using the URL & method & data.
https://www.npmjs.com/package/run-middleware
For example:
module.exports = function() {
return function(req, res, next) {
// getAlias would get an object {alias:"alias/path",source:"/real/path"} or null
var alias = getAlias(req.url);
if(alias) {
res.runMiddleware(alias,(status,data)=>(res.status(status).send(data))
}
next();
};
};
Related
While routing in Express is quite straightforward I'm having trouble adjusting it to paths that end with a /.
For example, suggest I define the following route:
app.get('/about', (req,res) => res.render('about'));
Now if someone navigates to www.example.com/about the about view is rendered. However, if that same person navigates to www.example.com/about/ the route I specified above will not work. Some people (me included) have gotten used to naturally adding a closing / at the end of paths. I read the Express routing documentation page but it seems the developers were oblivious to this possibility. The only solution I've found thus far is to use regular expressions for each and every route to account for this variation. For example, the route above would become:
app.get(/\/about\/?/, (req,res) => res.render('about'));
Is there a more elegant (or built in) solution to allow for path with a closing / in Express?
This question has already been answered in https://stackoverflow.com/a/15773824/515774
Basically, you will need to add a middleware which will strip the trailing slash and make a redirect request, which will solve your problem.
Following is the code snippet from the previous answer.
app.use(function(req, res, next) {
if (req.path.substr(-1) == '/' && req.path.length > 1) {
var query = req.url.slice(req.path.length);
res.redirect(301, req.path.slice(0, -1) + query);
} else {
next();
}
});
To avoid redirect, you can just rewrite the URL. Reference https://stackoverflow.com/a/13446128/515774
Note: The browser URL stays the same using this approach.
app.use(function(req, res, next) {
if (req.url.slice(-1) === '/') {
req.url = req.url.slice(0, -1);
}
next();
});
Does any one knows an example or could explain here how node.js and express would have to route for a multilanguage site? I'm using i18n-node for translation and folder like routing ( /es/, /de/ , etc ) for different languages. This all are static routes but I also have routes like apiRoutes.route('/user/profile') using 'app' at the begining ( app.get('/app/user/profile') so please consider this in your answer so is NOT necesary route to : app.get('/es/app/user/profile') .
having 15 routes like this now:
app.get('/terms', function(req, res) {
res.render('terms',{
...
});
});
how it have to be set for routes like:
app.get('/es/terms', function(req, res) {
res.render('terms',{
...
});
});
Should I duplicate this routes and add for example a locale for
each like:
app.get('/es/terms', function(req, res) {
res.render('terms',{
...
});
});
Or Should do something like:
if cookie['lang'] && cookie['lang'] is in locales
// then redirect to /:lang/terms
else
// show default language in /terms
if req.headers["accept-language"] && req.headers["accept-language"]
// then redirect to /:lang/terms
else
//show default language in /terms
Or there is another way I should approach this that follows good practices or is better respecting standards?
Miro's Answer in :
How can I get the browser language in node.js (express.js)? says I should use app.all('*', ...
Is this all I need?, ..still, it might have a syntax error or i'm not understanding well this two parts
var rxLocal = /^\/(de|en)/i;
...
app.get(/\/(de|en)\/login/i, routes.login);
thanks in advance
You need to consider 2 things :
1. How get the local :
Accept-Language
The HTTP protocole define the Accept-Language header to manage the local. This is a normalized method. You can access it with the req.acceptsLanguages method of express.
+Normalized
+Natively support by brower
-Not easy to by passe by the end user
Path / Cookies
You can get the local from the path. In express it can be do with a parameter patter like /:local/rest/of/path and retrieve in the request object with the req.param method.
You can also get the information from the cookies with the req.cookies properties (don't forgot to set it).
Both
To increase the user experience you can mix the both method. For exemple get the default language from the HTTP header send by the browser but permite to the user to override this in you application and store this parameter in the cookies.
2. Use the local:
Each methods to get the local can be used from different way. I will
use random of them in exemple but they are all compatible.
Top level configuration.
In case of you use a template Engine and you controller can be local agnostic. You can use a middleware to get the local information and configure the render engine.
app.use('/:local' (req, res, next) => {
let localKey = req.param('local');
res.locals = // Some ingenious method to get the locales from localKey
next();
}
Check res.locals and your engine documentation.
Use it in controller.
If the local is part of the contoller process. You can get directly is value in controller.
In case of you use a complexe method to determine the final value of the local, you can also use a middleware to determine this value and enrich the request with it.
app.use((req, res, next) => {
let local = req.cookies.local;
if(!local) local = req.acceptsLanguages();
if(!local) local = 'en-US';
req.local = local;
}
Both
You can use both method too. It depend of what you need. Find the best way to get a maintainable code and avoid replication for your use case.
When you use middle where witch impact the controllers, be sure you declare them before your routes.
You can use a route parameter to get the locale from the URL, like this:
app.get('/:lang/terms', function (req, res) {
if (req.params === 'es') {
res.send('¡Hola!');
else {
res.send('Hi!');
}
});
The colon character tells Express to put whatever is between the first to slashes of the path in req.params.lang.
See express routing documentation for details.
I'm trying to include i18n in keystone.js app. I'm doing it like in this example https://gist.github.com/JedWatson/9191081, and it works, but my problem is to get current locale in view. I'm using a middleware for setting locale by url param:
// middleware.js
exports.setLocale = function (req, res, next) {
if (req.params.lang) {
req.setLocale(req.params.lang);
}
else
res.redirect('/ru/');
next();
};
// index.js
keystone.pre('render', middleware.setLocale);
and routing
app.get('/:lang/', routes.views.index);
app.get('/:lang/blog/:category?', routes.views.blog);
app.get('/:lang/blog/post/:post', routes.views.post);
...
Default locale is 'ru'. But in my view
view.on('render', function (next) {
console.log('on render', req.getLocale());
next();
});
console.log('before render', req.getLocale());
view.render('blog');
on route /en/blog it outputs
------------------------------------------------
KeystoneJS Started:
qalqan is ready on default port 3000
------------------------------------------------
before render ru
on render en
GET /en/blog 304 373ms
So, as i understand, locale is changed after varibales sended to view. Is threre any way to set it before rendering? I know, that i can get it by req.params from url param lang in each view, but i want do it by middleware for all views.
UPDATE: Since i18n version 0.7.0 your code should work.
It seems a bit counter-intuitive, but I think you should not touch the routing to avoid repetition of the lang parameter.
You are exactly on the right path though and here is what I did:
// routes/middleware.js
exports.detectLang = function(req, res, next) {
var match = req.url.match(/^\/(de|en)([\/\?].*)?$/i);
if (match) {
req.setLocale(match[1]);
// Make locale available in template
// (necessary until i18n 0.6.x)
res.locals.locale = req.getLocale();
// reset the URL for routing
req.url = match[2] || '/';
} (else) {
// Here you can redirect to default locale if you want
}
next();
}
Now just use the middleware before all the other res.locals are defined:
// routes/index.js
keystone.pre('routes', i18n.init);
keystone.pre('routes', middleware.detectLang);
Now you can prepend all URLs with '/' + res.locals.locale and the user will keep their locale everywhere around your app.
In my ExpressJS app, several of my urls handlers have the following logic:
See if the user has permission to access a resource
If so, continue
Else, redirect to the main handler.
Is there a way to insert a pre-handler for certain url handlers, via ConnectJS or ExpressJS?
I know I can do it globally, for all handlers, (which I do to insert missing headers as a result from IE's broken XDR).
But, can I do this for a subset of handlers?
I do something like this:
lib/auth.js
exports.checkPerm = function(req, res, next){
//do some permission checks
if ( authorized ) {
next();
} else {
res.render('/401');
return;
}
};
app.js
var auth = require('./lib/auth');
...
app.get('/item/:itemid', auth.checkPerm, routes.item.get);
You can stack middleware before your final route handler like the above line has. It has to have same function signature and call next();
If I understand this question correctly, you know about:
// This is too general
app.use(myAuthMiddleware());
And you are aware that you can add it manually to certain url-handlers:
app.get('/user/profile/edit', myAuthMiddleware(), function(req,res){
/* handle stuff */ });
// but doing this on all your routes is too much work.
What you might not know about express' mounting feature:
// Matches everything under /static/** Cool.
app.use('/static', express.static(__dirname + '/public'));
Or app.all():
// requireAuthentication can call next() and let a more specific
// route handle the non-auth "meat" of the request when it's done.
app.all('/api/*', requireAuthentication);
I am using latest versions of NodeJS and ExpressJS (for MVC).
I usually configure my rest paths like this, for example:
app.get('/archive', routes.archive);
Now i want my /admin/* set of URLs to be secured, I mean I need just simple authentication, it's just a draft.
When a user tries to access, for example, /admin/posts, before sending him the corresponding view and data, I check for a req.session.authenticated. If it's not defined, I redirect to the login page.
Login page has a simple validation form, and a sign-in controller method: if user does send "right user" and "right password" I set the session variable and he's authenticated.
What I find difficult, or I don't understand, is how to actually make the "filter" code, I mean, the auth check, before every /admin/* path call.
Does this have something to do with "middleware" express functions?
Thank you
Yep, middleware is exactly what you want. A middleware function is just a function that works just like any other Express route handler, expept it gets run before your actual route handler. You could, for example, do something like this:
function requireLogin(req, res, next) {
if (req.session.loggedIn) {
next(); // allow the next route to run
} else {
// require the user to log in
res.redirect("/login"); // or render a form, etc.
}
}
// Automatically apply the `requireLogin` middleware to all
// routes starting with `/admin`
app.all("/admin/*", requireLogin, function(req, res, next) {
next(); // if the middleware allowed us to get here,
// just move on to the next route handler
});
app.get("/admin/posts", function(req, res) {
// if we got here, the `app.all` call above has already
// ensured that the user is logged in
});
You could specify requireLogin as a middleware to each of the routes you want to be protected, instead of using the app.all call with /admin/*, but doing it the way I show here ensures that you can't accidentally forget to add it to any page that starts with /admin.
A even simpler approach would be to add the following code in the App.js file.
var auth = function(req, res, next) {
if(isAdmin) {
return next();
} else {
return res.status(400)
}
};
app.use('/admin', auth, apiDecrement);
As you can see the middleware is being attached to the route. Before ExpressJS goes forward, it executes the function that you passed as the second parameter.
With this solution you can make different checks before displaying the site to the end user.
Best.
Like brandon, but you can also go the connect route
app.use('/admin', requireLogin)
app.use(app.router)
app.get('/admin/posts', /* middleware */)