Express.js Series: A Beginner's Guide to Routing Fundamentals

Getting started with Express is not the easiest thing to do, if you have read my previous post on Express' app.js you will have a basic understanding of a default Express app by now. If you haven't, I encourage you to read it. There is one topic that I didn't cover in that article and that is arguably the most important part of an Express app, routing.

The Basics

To make use of Express' routing capabilities you need to initiate a new Express Router.

var express = require('express');
var router = express.Router();

When you have the router object you can use its five routing methods.

.get() - when you visit a website you make a GET request. You can get data from a URL in a GET request too.

.put() is a method used to update data. Whenever I want to update data in one of my apps, I use a PUT request.

.post() is a method used everywhere for posting data to a server / app. You'll want to use this for submitting forms.

.delete() is pretty self-explanatory. Use this method to delete data.

.all() is the method which acts as any of the above. If you add the .all() method to a route, and pass in the next() parameter, you can run a function for every request. If you add the .all() method on top of your other routes that need authentication it could save you a lot of time and effort.

There are two ways of using the router methods. You can define one route (for example /home) and attach the methods to it, or you can create a new method for each route.

// Method 1
router.route('/hello')
	.get(function (req, res, next) { ... })
    .post(function (req, res, next) { ... });
    
// Method 2
router.get('/hello', function (req, res) { ... });
router.post('/hello', function (req, res) { ... });

Method one is nice when you have some sort of API and want to accept different methods for the same route / page. I personally prefer method two because I rarely use more than one method on a route.

Your First Route

I have decided to show you how to do routing with the Jade view engine because it's Express' default view engine and I like it. If you don't like it you can always choose another view engine. The information here applies to all view engines in Express, only their syntax is different.

Alright, let's create our own first route!

/app.js

var express = require('express');
var path = require('path');
var app = express();
var router = express.Router();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(router);
app.use(express.static(path.join(__dirname, 'public')));

router.all('/', function (req, res, next) {
  console.log('Someone made a request!');
  next();
});

router.get('/', function (req, res) {
  res.render('index');
});

app.listen(3030);
module.exports = app;

/views/index.jade

doctype html
html(lang="en")
  head
    title Express Routing
  body
    h1 Express Routing
    p The Definitive Guide

This uses the app.js from my previous post, but I removed all the things we do not need. Normally you don't want all your routes in one files but it is more convenient for this post.

If you're curious what good seperating of routes looks like, check out the Ghost routes folder

After running node app.js you should see something that looks like:

Let's break the important parts down.

app.use(router) is the one most people tend to forget sometimes (including me). If you forget to tell Express to use your router, the router will not work (duh).

app.use(express.static(path.join(__dirname, 'public'))) if you want to include external CSS, JavaScript and images add this. It will tell Express to look in your /public folder. For example img(src="img/dog.png", alt="woof") would show /public/img/dog.png.

router.all('/', function (req, res, next) { ... }) is an example of the .all() method. Don't worry about the req and res parameters for now, I'll cover them in a bit. What's interesting though is the 3th parameter next. This is an optional parameter you can give to a route, when executed it takes you to the next route / middleware. Without the next() function or ending the response we'll never get any data back from our app.

router.get('/', function (req, res) { ... }) is where the magic happens. We render our index.jade file here and send it to the browser. We don't need to specify any folders or extensions for our views because we specified our view engine and folder already. There's also no need for any next() because of res.render().

Request & Response

Usually your application revolves around sending and receiving data, doing something with the users request and giving them a useful response.

A request is something that your server/app gets from a visitor. Information about the user is sent with the request, such as headers. With the right middleware you could also read cookie data and session data. The user can also specify data to send if you allow to do so. For example in a login form, the username and password will be sent with the request.

The response is something your app gives the user. This can be a webpage, an image or data like JSON and XML. You decide the response, usually based on user input. Just like that login form I just mentioned, you tell the user whether his/her username and password are correct in the response.

POST data

Let's do something more interesting than rendering a view without any data. We'll create a secret page that's only visible to people who know the password.

We already have our index.jade, so let's just modify it to accept a password.

body
 h1 Enter the top secret password
                                        
form(action="/secret", method="post")
  input(type="password", name="secret")
  input(type="submit", value="Show me!")

As you can see you really have got this mobile first approach down in this app. Unfortunately this amazing UI won't do anything useful yet. We need to add a new route for the /secret page. Since we're POSTing this super secret password it's going to be a POST route.

To be able to read POST data, we'll need some middleware. As described in my previous blogpost we're going to need body-parser. If you've installed Express from the express-generator you'll already have it. If you started from a clean slate you need to npm install body-parser --save to install it.

var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

It's important to call app.use() with the bodyParser methods before you initialize the Express router. Alright! We're ready to create the route!

router.post('/secret', function (req, res) {
  var secret = req.body.secret;
  res.end('Password: ' + secret);
});

Because we haven't created a new view yet we'll just print out the secret. We do this with response.end('message'). When we go back to our home page and submit a password we should see:

Alright! We can now read received POST data. When we try to refresh though, it doesn't quite do what we want. This is because we send a POST request from our form on the index page but as soon as we refresh the page it becomes a GET request.

GET Parameters

Whereas POST data is submitted through forms, GET parameters can be read from URLs. For example YouTube uses it for their videos.

There are 2 ways to read GET data in Express, either as a parameter or a query. I'll explain the differences below, continuing with our secret route.

// Parameters
router.get('/secret/:key', function (req, res) {
  var key = req.params.key;
  res.end('Password: ' + key);
});

// Query
router.get('/secret*', function (req, res) {
  var key = req.query.key;
  res.end('Password: ' + key);
});

req.query.key looks like /secret?key=MyKey. Queries are nice because you can read multiple values in different orders. The syntax is as follows:

/pagename ? key = value & otherKey = otherValue (spaces just for readability). The result would be:

req.query = {
  key: 'value',
  otherKey: 'otherValue'
}

The downside of queries is that URLs can get really long and messy. For cleaner and more strict URLs use parameters. You can also add multiple values like with queries but you decide the order.

If your route string looks like /pagename/:key/:otherKey your url needs to look like /pagename/value/otherValue to get the same result. This looks much cleaner but if you change the order to /pagename/otherValue/value your parameters object will look like:

req.params = {
  key: 'otherValue',
  otherKey: 'value'
}

Now we've got that down, we'll continue creating our secret page.

Creating A Template

We're going to create another view template in our view folder called secret.jade. This will be the secret page for both GET and POST requests.

secret.jade

body
  if authorised
    h1 Welcome to my secret page, you have made it!
    img(src="http://bit.ly/1j7LCvf")
  else
    h1 You're not authorised to view this page

    form(action="/secret", method="post")
      input(type="password", name="secret")
      input(type="submit", value="Authorise")

Yes that is an if statement inside our page. This is where the real power of a view engine lies. If you want to know more about jade you should check out their documentation, it's really good.

To render data in a view you pass in an object as second parameter of the res.render() method.

Finally let's update our routes!

var secretKey = "goat";

router.post('/secret', function (req, res) {
  var secret = req.body.secret;
  var user = {};
  user.authorised = secret === secretKey ? true : false;
  res.render('secret', user);
});

router.get('/secret/:key', function (req, res) {
  var secret = req.params.key;
  var user = {};
  user.authorised = secret === secretKey ? true : false;
  res.render('secret', user);
});

router.get('/secret', function (req, res) {
  var user = {
    authorised: false
  };
  res.render('secret', user);
});

The order of the views are important. If you have .get('/secret', ...) before .get('/secret/:key', ...) the request won't make the second route unless you add the next() parameter and execute it. If you'd like to have your routes in chronological order you should update you're route to look like this.

router.get('/secret', function (req, res, next) {
  if (!req.params.key) { next(); }
  ...

Conclusion

Because Express it like no other web framework it's a lot of fun, but also harder to get into. I hope this helps you with your Express journey! If you have any questions or feedback don't hesitate to leave a reply or a tweet.

I plan on doing a post next week on authentication using Express, stay tuned!