How to build large scale express.js sites - javascript

I am pretty new to the node.js and express.js landscape. However I have been trying to wrap my head around the code behind creating a site (20+ pages for example) in express.js without the code getting pretty large.
A route, when using a view engine, points the incoming request to index or whatever page they requested. After running the default express-generator with the view engine set to HBS you get something like
app.use('/', index);
This points to the index.js file under the routes folder that contains something like
router.get('/', function(req, res, next){
res.render('index', {title: 'Express'});
});
And that then renders the index.hbs file to show what I want on the page.
My problem is this. Would you have to have a route for each page you wanted like a profile page, login page, about page, etc? If so wouldn't this create a lot of lines of code in routes if you had 20+ routes? Example would be in the app.js
app.use('a', a);
app.use('b', b);
...
app.use('z', z);
then each would require a corresponding route js file.
I assume there is a cleaner way of doing routing or perhaps I am over thinking this?
Any light on this concern/question of mine would be amazing.

In my express servers I break routes into different sections called "components" Each components can correspond to a give page if you're doing server-side rendering or it can correspond to a set of API routes.
Each component can have controllers to handle each route, and each controller can borrow from a handful of reusable actions.
Here's an example of an express server component I made(this example is for a set of API routes but the same architecture can be used for sets of hbs server-side rendering routes):
https://github.com/AkyunaAkish/react-redux-node-express-todo-list-boilerplate/tree/master/server/components/todos

It depends on how similar your routes are:
If they basically all have the same functionality, I'd put them in the same file.
If there are slight variations, I'd create a separate class containing the core functions, and then call to what ever is needed separately.
If they are completley different, put them all in separate files
This will give you a solid outline of how to do each:
How to include route handlers in multiple files in Express?
Node.js: Configuration and routes in a different file
How to include route handlers in multiple files in Express?

Generally, you make route files around areas of concern, though a given router can define whatever endpoints make sense. So for example, let's say your site has several pages that are fairly static (e.g. your "about" and "index" examples, and then several that are all based on blog entries (creating, listing, viewing, etc) and then several around users (user profiles and so on). You would probably create one router for each set of things, e.g.:
// ./routes/index.js
router.get('/', (req, res) => { res.render('index', {title: 'Express'}); });
router.get('/about', (req, res) => {res.render('about', {title: 'Express'});});
// etc
conceptually, you could use that more simply to pull the "index" or "about" values from the URI, but I'm assuming you'll do other things like assigning variables and such.
Then in another file
// ./routes/blog.js
router.get('/blog', (req, res) => {
// do whatever to fetch info fromt eh DB and render it...
});
And so on.

Related

Node.js: Changes on route settings don't show effect

For some strange reason, some changes on my route settings (MEAN environment, Node v0.12.2 and express 4) don't show effect any more!? Particularly instructions where I respond to client requests using ".sendfile()".
app.get('/', function(req, res){
res.sendfile("/public/index.html"); // <-- trying to exclude or change this
console.log("debug message"); //added later, never shown!!
});
Excluding or altering the sendfile instruction in the example above doesn't change anything - index.html is alyways being delivered upon request. Not even simple debug messages like console.log are shown any more?! Here's what I checked:
restarted Node.js server and computer several times
checked for duplicates of routes.js file
checked for duplicates of home route ("/")
cleaned browser cache
even deleted the ENTIRE route, site still delivered upon request!?!?
Maybe there is some kind of server-side cache that needs to be wiped?! I got no idea any more of what's wrong. Suggestions anyone?
There are changes in express 4 with respect to express 3. In express 4 we have got a new method app.route() to create chainable route handlers for a route path and a new class express.Router to create modular mountable route handlers. For more details ref: Moving to Express 4 What you can do is try using code in following order:
app.route('/')
.get(function(req, res) {
res.render('index');
});
And its also good serve static files by adding:
app.use(express.static(path.join(__dirname, '../public')));
Good luck...:)

Understanding Express routing in conjunction with AngularJS (using mean.js boilerplate)

I am developing a web application with the mean.js boilerplate code. Recently I added a view to my application (the 'analysis' page) by creating an analysis module and added some route logic with the following code.
//Setting up route
angular.module('analysis').config(['$stateProvider',
function($stateProvider) {
// Projects state routing
$stateProvider.
state('imageAnalysis', {
url: '/image_analysis',
templateUrl: 'modules/analysis/views/tools.client.view.html'
});
}
]);
This works just dandy, I can go to homepage.com/image_analysis and see my tools.client.view.html page. My goal now is to have this page only visible to user who are logged in. My first approach to this was to check if the user was logged in the angular routes logic, this has proven to be difficult for me. I also read that check authentication in the front end is very unsafe. So I've decided to check if the user is logged in with Express routing.
Here is an example of what Express routing looks like for one of my other AngularJS modules
module.exports = function(app) {
var users = require('../../app/controllers/users.server.controller');
var projects = require('../../app/controllers/projects.server.controller');
// Projects Routes
app.route('/projects')
.get(users.requiresLogin, projects.list)
.post(users.requiresLogin, projects.create);
app.route('/projects/:projectId')
.get(users.requiresLogin, projects.read)
.put(users.requiresLogin, projects.hasAuthorization, projects.update)
.delete(users.requiresLogin, projects.hasAuthorization, projects.delete);
// Finish by binding the Project middleware
app.param('projectId', projects.projectByID);
};
Notice the users.requiresLogin function which check if a user is logged in and redirects the user to the signin page if they are not. I want to take advantage of this function. Some things I dont quite understand in the boilerplate code are what is the purpose of app.route()? I think this might be a middleware but I am not sure what it does.
Here are some attempts I've take taken at messing around with express routing. These attempts are in my analysis.server.routes.js file that I created.
Attempt 1
module.exports = function(app) {
var users = require('../../app/controllers/users.server.controller');
// analysis routes
app.get('/image_analysis', function(req, res) {
console.log('helloworld');
console.log('helloworld');
console.log('helloworld');
console.log('helloworld');
console.log('helloworld');
console.log('helloworld');
});
};
I thought this would log 'helloworld' when I access the image_analysis page, but it didnt appear to do anything.
Attempt 2
module.exports = function(app) {
var users = require('../../app/controllers/users.server.controller');
// analysis routes
app.route('/image_analysis')
.get(users.requiresLogin);
};
Based on the other code, I thought this might work to require login for the analysis page. But it did nothing.
Any help would be much appreciated! Thanks everyone, I am struggling to grasp express routing at the moment and stackoverflow has been a tremendous help.
I don't have a solution, or not at least at this moment that I could think of.
However I can explain to you how things work and why a solution might not be trivial.
what is the purpose of app.route()
This defines your routes.
app.route('/projects/:projectId')
.get(users.requiresLogin, projects.read)
.put(users.requiresLogin, projects.hasAuthorization, projects.update)
.delete(users.requiresLogin, projects.hasAuthorization, projects.delete);
What that code does is when a client requests a URL like one of the following
GET /projects/5562ff08de4f70180bba3083
PUT /projects/5562ff08de4f70180bba3083
DELETE /projects/5562ff08de4f70180bba3083
it first executes that middleware function users.requiresLogin,
which is defined to be common in all methods (.get .put .delete)
and then their respective middlewares in that order
projects.read | projects.hasAuthorization ...
On how Angluar routing works in contrast to Express routing:
Angluar routing is all done client-side. It's all achieved through Javascript
changing the addressbar url and displaying the view attached to that route.
Your Angular app is served by the server through a single (Express) route - / (homepage)
So again, your entire Angular app loads thorough just 1 route on the server-side,
and then Angular takes over the routing to client-side through it's own ui-router.
Now it must be starting to seem obvious that it's not trivial to have server-side authentication check
interfere between client-side routing.
You can only have server-side authentication work with routes that server actually is in control of serving.
Those are mostly just your API routes, in your case.
Attempt 1
app.get('/image_analysis', function(req, res) {
console.log('helloworld');
I thought this would log 'helloworld' when I access the image_analysis page, but it didnt appear to do anything.
It indeed should have! Unless.. there's a route that comes before it that already serves all requests
app.get('*', function(req, res) {
res.send(angularApp); // your angular app is served here
});
// No request would *need* to go beyond past this point
// because it already got served above
app.get('/image_analysis', function(req, res) {
// so this isn't reached.
console.log('helloworld');
});
So that's why it doesn't work.

Send URL string to other script with Node.js

I am starting to learn Node.js, using Express with Jade and Mongoose as my default toolbox. I used to develop in PHP, migrated to Python, and learned MVC through Django. Having a large client-side JS game and some inspiration from Mozilla.org, I am willing to make a multiplayer game -- and saw this as a noncommercial opportunity to learn Node: I can take my time with it.
However, I ran into a problem. I'm not trying to write an MVC system on my own, just to separate my site's "apps" like most MVCs do. The question is probably basic -- having this chunk of code:
app.get(/^blog/, function(req, res) {
require("./blog")();
});
... I understand the basics of Node/Express' URL masking, however I need to pass the rest of the URL string (everything that's after mysite.com/blog) to another URL parsing script, inside the blogapp.
I googled around for a while and couldn't find a good solution. I even found a full tutorial on building an MVC scheme in Node and Express written for an older Express version, but that's a bit over the top for now. Can you provide me a simple solution?
I think blog/index.js should look something like this:
module.exports = function(urlstring) {
if(urlstring.indexOf('post') != -1) {
// do stuff...
}
else if(urlstring === '/') {
// return home.jade or something
}
};
I hope I'm being clear. Thanks in advance!
With express there is no need to parse your URLs on your own. I guess you'll want to build your blog URLs somehow like this
/blog Show a list of blog posts
/blog/post/1 Show blog post with id '1'
With express 4 you can set up a router for your blog path or a mounted app. Mounted apps allow you to let an app handle all sub URLs of a base URL path. See the express documentation for more detail.
I'd like to demonstrate how you can use the express 4 router together with the mounting feature of express to build blog routes.
// Set up express app
var app = express();
// Set up a router
var router = express.Router();
router.get('/', function(req, res) {
// Show a list of blog posts
}
router.get('/post/:id', function(req, res) {
// Show a single blog post
}
// Mount router
app.use('/blog', router);
A benefit of this solution is that your routes registered in the router always get relative URLs with out the /blog prefix so you may reuse your blog routes in some other project under a URL like /companyblog.

.redirect() from mounted middleware with relative destination fails

I want to restrict a certain subtree only to authenticated users. The basic setup is as follows (fat removed):
app.use(express.bodyParser())
.use(express.cookieParser('MY SECRET'))
.use(express.cookieSession())
.use('/admin', isAuthenticatedHandler)
.use('/admin', adminPanelHandler);
Where the handler functions is:
isAuthenticatedHandler = function(req, res, next) {
if (!req.session.username) {
res.redirect('login');
} else {
next();
}
};
The problem is that even though I provide the redirect destination as a relative path 'login', it doesn't lead to <mount_point>/login i.e. /admin/login but to /login which of course throws a 404.
From the expressjs API reference:
This next redirect is relative to the mount point of the application.
For example if you have a blog application mounted at /blog, ideally
it has no knowledge of where it was mounted, so where a redirect of
/admin/post/new would simply give you `http://example.com/admin/post/new`,
the following mount-relative redirect would give you
`http://example.com/blog/admin/post/new`:
res.redirect('admin/post/new');
Am I misreading this?
The issue here is that while you are using your middleware off of /admin, your app itself is not mounted at /admin. Your app is still off of the root, and your configuration simply says to only use your isAuthenticatedHandler middleware if the request comes in off the /admin path.
I whipped together this gist. Notice how it uses 2 Express applications, one mounted inside the other (line 23 pulls this off). That is an example of mounting the application at a different point rather than just putting a given middleware at a given point. As presently written, that example will give you an endless redirect, since the isAuthenticatedHandler fires for everything off of / in the child application, which equates to /admin overall. Using 2 separate applications might introduce other issues you're not looking to deal with, and I only include the example to show what Express means when it talks about mounting entire applications.
For your present question, you'll either need to follow what Yashua is saying and redirect to /admin/login or mount your admin interface as a separate Express application.
What are you trying to achieve? Why not just redirect to '/admin/login' ? And the mount point they are talking about is the place where your Express app is located, not necessarily the current URL. So /blog might be setup on your server to be the root of your app while / might be a totally different app. At least that's the way I read this.

Multiple routers vs single router in BackboneJs

All examples on Backbone I've seen use one router for the whole application, but wouldn't it make sense to have a router for each single part of your app (header, footer, stage, sidebar)? Has anyone built apps with more than one router and what are your experiences?
Let's think about a complex app with nested views: Wouldn't it be better when a view has its own router that handles the display of the subviews, than having one big router that has to inform the main view to change its subviews?
The background of this question: I've see a lot of parallels of the router in backbone and the ActivityMapper in GWT. The ActivityMapper is only responsible to get the right presenter for a given route and a given container in the DOM.
i wrote an app (still writing) with multiple routers in it.
however it is not like you think, it is more module based and not a router per view or anything like that.
for example,
say i got two big modules in my app, 1 handling all books, and 1 for the users.
books have multiple views (as do users), a list view, detail view, edit view, etc etc...
so each module has its own router,
which stands for its own set of urls:
// user module...
var userRouter = Backbone.Router.extend({
routes: {
"users": "loadUsers",
"users/add": "addUser",
"user/:id": "loadUser",
"user/:id/edit": "editUser"
}
// ... rest dropped to save the trees (you never know if someone prints this out)
});
// book module
var bookRouter = Backbone.Router.extend({
routes: {
"books": "loadBooks",
"books/add": "addBook",
"book/:name": "loadBook",
"book/:name/edit": "editBook"
}
// ... rest dropped to save the trees (you never know if someone prints this out)
});
so, it is not like my two routers are competing for the same route, they each handle their own set of routes.
edit
now that I had more info via Elf Sternberg, I know it isn't possible by default to have multiple routers match on the same route. without a workaround like overriding the backbone history or using namespaces in routes and regexes to match these routes.
more info here: multiple matching routes
thanks Elf Sternberg for the link.
I just wrote a blog post on Module-Specific Subroutes in Backbone, which allow a "subroute" to be defined which pays attention to everything after the prefix for that route.
Check out the blog entry for more explanation: http://www.geekdave.com/?p=13
This means you don't have to redundantly define the same prefix over and over, and you can also lazy-load subroutes as modules are accessed. Feedback is most welcome!
There is a limited but important case when it makes sense to use multiple Routers. If you need to expose only a subset of your application's routes & views based on data collected at runtime (perhaps login credentials - e.g., manager vs. staff can see & navigate between different sets of views) you could instantiate only the appropriate Router & View classes. This is significant because routes can be bookmarked and sent from user to user. Of course, you still need checks on the server to ensure that an unauthorized user isn't issuing requests after navigating to a view they arrived at via a bookmark sent by an authorized user. But it's better to design the application so the unauthorized view is just not generated.

Categories

Resources