Scott Smith

Blog Tutorials Projects Speaking RSS

Twitatron: Building a Production Web App With Node - Views & Controllers

Welcome to part 2 of the Twitaron series

  1. Getting started
  2. Views & Controllers
  3. User Accounts
  4. Under development…

In our previous article we started with the basics and built a web application capable of serving static content, compressing that content, and implementing cache headers.

In this installment of the Twitatron series, we will be diving into Views and Controllers.

Getting setup

Our view engine of choice will be Jade. Jade is a terse language for writing HTML templates. It is capable of producing HTML, supports dynamic code, and supports reusability. You can find a tutorial here to learn more about Jade.

First thing we need to do is create a directory to store our views. If you don’t already have a directory named views you will need to create one now.

We will now need to tell Express about the view engine we wish to use.

Open up server.js and update it with the following code after the static middleware code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...

// Add static middleware
var oneDay = 86400000;
app.use(express.static(__dirname + '/public', { maxAge: oneDay }));

// Add jade view engine
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');

// Create our Express router
var router = express.Router();

...

Next, we need to install the Jade npm package.

1
npm install jade --save

What we did was add two pieces of middleware to our Express application. First, we told it to set the directory for our views to /views. Second, we told it to use Jade as our view engine.

We are now ready to create our first view.

Our first view

In the views directory create a new file named home.jade and add the following code to it.

1
2
3
4
5
6
7
doctype html
html
  head
    title Twitatron
  body
    h1 A Twitatron view has been born!
    img(src="/img/birdatron-small.jpg")

Next, we need to create a route at the root of our application to render this view. Open up server.js and update it with the following code. Be sure to remove the “dummy” route we made in the first tutorial that just returned the text “Twitatron”.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...

// Create our Express router
var router = express.Router();

// Landing page route
router.get('/', function(req, res) {
  res.render('home');
});

// Register all our routes
app.use(router);

...

Finally, because we now have a view, we do not need the index.html file inside our public directory. Go ahead and delete it.

What we have done is setup a new route to handle GET requests to /. When a browser requests http://localhost:3000/ it will execute the anonymous function we defined to render the view named home. Because we already defined the directory for our views to be views, Express will look for a view named home.jade, render it into html, and return it back to the requesting client.

Go ahead and test out your code to make sure everything is working. You should get back a response with the text “A Twitatron view has been born!” along with the Twitatron bird image.

Layout and partials

The next thing to update in our application is to implement a layout view and some partial views. This will help us reduce a significant amount of view code through reuse.

The first thing we will create is our layout view. This will define the general layout of our application and will be used in most of our other views. Inside the views directory, create a filed named layout.jade. Update it with the following code.

1
2
3
4
5
6
7
8
9
doctype html
html
  head
    title Twitatron
    include partials/head
  body
    include partials/navigation
    block content
    include partials/footer

You will notice that our layout contains references to 3 partial views. Let’s go ahead and create these now. First, create a sub-directory inside the views directory named partials. Next, create three new views named: head.jade, navigation.jade, and footer.jade. Update each one as follows.

Head

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
meta(charset='utf-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(name='description', content='Automatically monitor your Twitter account for mentions.')
meta(name='keywords' content='twitter, mentions, api, rss, email, storage, bookmark')
meta(name='csrf-token', content=_csrf)
link(rel='apple-touch-icon', sizes='57x57', href='/apple-touch-icon-57x57.png')
link(rel='apple-touch-icon', sizes='114x114', href='/apple-touch-icon-114x114.png')
link(rel='apple-touch-icon', sizes='72x72', href='/apple-touch-icon-72x72.png')
link(rel='apple-touch-icon', sizes='144x144', href='/apple-touch-icon-144x144.png')
link(rel='apple-touch-icon', sizes='60x60', href='/apple-touch-icon-60x60.png')
link(rel='apple-touch-icon', sizes='120x120', href='/apple-touch-icon-120x120.png')
link(rel='apple-touch-icon', sizes='76x76', href='/apple-touch-icon-76x76.png')
link(rel='apple-touch-icon', sizes='152x152', href='/apple-touch-icon-152x152.png')
link(rel='icon' type='image/png', href='/favicon-196x196.png', sizes='196x196')
link(rel='icon' type='image/png', href='/favicon-160x160.png', sizes='160x160')
link(rel='icon' type='image/png', href='/favicon-96x96.png', sizes='96x96')
link(rel='icon' type='image/png', href='/favicon-16x16.png', sizes='16x16')
link(rel='icon' type='image/png', href='/favicon-32x32.png', sizes='32x32')
meta(name='msapplication-TileColor' content='#9f00a7')
meta(name='msapplication-TileImage' content='/mstile-144x144.png')

This view contains all our information for the head section of our HTML. Right now there are references to many icons for our application that do not yet exist. Don’t worry about that for now as we will be making these in future tutorials.

Navigation

1
2
3
header
  div
    a(href='/') Twitatron

Pretty simple navigation piece for now. This will become more robust as we progress through the series.

Footer

1
2
3
4
5
footer
  div
    small © Twitatron 2015
    small Created by
      a(href="http://scottksmith.com", target="_blank") Scott Smith

The last thing we need to do is update our current view for the homepage to use the new layout view. Update home.jade with the following.

1
2
3
4
5
extends layout

block content
  h1 A Twitatron view has been born!
  img(src="/img/birdatron-small.jpg")

Our view now says to extend our layout view and use the content defined within the content block within the block content section in the layout.

Now, all future views we create can extend the layout view and thus provide a head section, navigation, and footer. Later we will update our layout to add things like CSS, JavaScript, and more.

Adding some dynamic code

By themselves, views are helpful but they are much more powerful when you support dynamic code. This can be done easily taking advantage of Express. Let’s go ahead and add a new element to our view that shows the IP address of the person making the request to our page.

Open up server.js and update our route handler as follows.

1
2
3
4
5
6
7
8
9
...

// Landing page route
router.get('/', function(req, res) {
  res.locals.ip = req.ip;
  res.render('home');
});

...

What this does is add the IP address of the client making the request to the res.locals.ip object which makes it available to our views. The res.locals object is where you can add anything you want available in views.

Next, update home.jade as follows with a new element.

1
2
3
4
5
6
extends layout

block content
  h1 A Twitatron view has been born!
  h2 You are visiting from #{ip}
  img(src="/img/birdatron-small.jpg")

This example is more to illustrate how to add dynamic code to your view from within your application. In future articles, we will dive more deeply into how to take advantage of this.

Controllers

The next thing we need to help make our application easier to understand and maintain are controllers. As our application grows, our route handlers will increase in quantity and complexity. We will do this by pulling out most functionality and placing them within separate modules.

Let’s start by updating the current route handler for we have the the root of our application.

If you don’t already have a directory named controllers you will need to create one now. Inside this folder, create a new file named home.js and update it with the following code.

1
2
3
4
exports.index = function(req, res) {
  res.locals.ip = req.ip;
  res.render('home');
};

What we have done is put our route handler code into a separate home controller module. Now we just need to update server.js to import the module and use it. Update server.jswith the following code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Load required packages
var path = require('path');
var express = require('express');
var compression = require('compression');

// Load controllers
var homeController = require('./controllers/home');

...

// Landing page route
router.get('/', homeController.index);

...

It may not seem like much now, but this type of pattern will help our application as we add a lot more functionality.

Using path to normalize paths

In two places, we have referenced the local file system using the __dirname global and a directory. To make our code more robust, we will take advantage of the core Node module path. The path module provides the function join that will join all the arguments into a normalized path.

For example, the following code would result in the path /Users/scott/Projects/twitatron/public

1
path.join(__dirname, 'public')

Update the two places within server.js where we are using the dirname global to use the path module.

1
2
3
4
5
6
7
8
9
10
11
//Old
app.use(express.static(__dirname + '/public', { maxAge: oneDay }));

//New
app.use(express.static(path.join(__dirname, 'public'), { maxAge: oneDay }));

//Old
app.set('views', __dirname + '/views');

//New
app.set('views', path.join(__dirname, 'views'));

Wrap up

While our application may not yet be pretty, we have a solid base to build and grow from. We have views, layouts, partials, controllers, and more. Stay tuned for more articles on this tutorial series on building production ready Node web applications.

If you found this article or others useful be sure to subscribe to my RSS feed or follow me on Twitter. Also, if there are certain topics you would like me to write on, feel free to leave comments and let me know.

Source code for this part can be found here on GitHub.