Express Web Framework has just recently become my framework of choice. I realize that Node seems like a bandwagon that everyone is jumping on, but consider this:

  • Express is tiny (~5MB footprint before additions)
  • No compiled references to add or maintain
  • A vibrant package community and brilliant dependency management system
  • No giant configuration files
  • It runs on any platform

It can be a bit frustrating to get comfortable with Express mostly due to the lack of extensive documentation for it's API's, but once you get the hang of it, it's like finally mastering the Shoryuken. For those of you too young to remember Street Fighter 2, I apologize for that reference.

But as Levar Burton used to say, "You don't have to take my word for it". Strap on your scarf and skinny jeans and let's take a look at how Express really holds up under some pretty fundamental requirements.

You can grab the completed code for this project or view the demo.

Registration And Login

Most apps start with a login/registration form. This sounds simple, but every time I build a form, I forget how much logic is involved. It's a good test for a web application framework since it involves request routing, form posting, session state, security, backend data operations and client/server data validation.

I am going to be using Everlive as my back-end data store for users. Telerik Everlive is a back-end service (sometimes referred to as a "Cloud") that offers data storage, user management, roles, email, push notifications and much much more. The best part is that developer accounts are completely free.

I'm just going to create a sample Everlive project called "Dashboard", and I'll think of something witty to set as the description.

 

Creating The Express App

Of course, NodeJS is a requirement for using Express as it's the platform on which express runs. Install Express (globally - means it's accessible from any place on your file system) using npm.

> npm install -g express

Then create the new "Dashboard" application.

> express dashboard

Express will then create a directory with that name and put the generated project files inside. It will ask you to navigate into that directory and install the dependencies when it's finished initializing the application.

> cd dashboard && npm install

Start the Express app with Node

> node app

You're up and running! Now it's time to get down to some real business.

Create login.jade and register.jade files. I'm not using the layout.jade for these pages since I usually reserve that for the application layout.

Add the following code to the login.jade page.

doctype 5
html
  head
    title Dashboard :: Login
    link(rel='stylesheet', href='//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css')
    link(rel='stylesheet', href='stylesheets/style.css')
  body
    .login.well
      h1  Please log in...
      hr
      form(id='login-form', method='POST', action='/login')
        .form-group
          label(for='username') Username
          input(type='text', name='username', class='form-control', id='username', placeholder='Enter username')
        .form-group
          label(for='password') Password
          input(type='password', name='password', class='form-control', id='password', placeholder='Enter password')
        .form-group
          button.btn.btn-default Login  
          a(href="register") Or Register

Quick note on Jade: I was really perturbed by Jade the first time I used Express since it will not allow you to write HTML. You can use EJS templates for that, but Jade is really quite nice once you get a feel for it. It does take some getting used to, so give it a chance.

Now add the code for the register.jade page.

doctype 5
html
  head
    title Dashboard :: Register
    link(rel='stylesheet', href='//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css')
    link(rel='stylesheet', href='stylesheets/style.css')
  body
    .login.well
      h1  Welcome aboard!
      hr
      form(id='register-form', method='POST', action='/register' novalidate)
        .form-group
          label(for='display') Display Name
          input(type='text', name='display', class='form-control', 
            id='display', placeholder='Enter display name')
        .form-group
          label(for='username') Username
          input(type='text', name='username', class='form-control', 
            id='username', placeholder='Enter username')
        .form-group
          label(for='password') Password
          input(type='password', name='password', class='form-control', 
            id='password', placeholder='Enter password')
        .form-group
          label(for='confirm-password') Confirm Password
          input(type='password', name='confirm-password', class='form-control', 
            id='confirm-password', placeholder='Confirm password')
        .form-group
          label(for='email') Email
          input(type='email', name='email', class='form-control', 
            id='email', placeholder='Enter email')    
        .form-group
          label(for='confirm-email') Confirm Email
          input(type='email', name='confirm-email', class='form-control', 
            id='confirm-email', placeholder='Confirm email')
        .form-group
          button.btn.btn-default Register
          a(href="login") Or Login

Now we have login and register pages, but we have no way to get to them since we haven't defined routes in Express for them yet.

Open app.js and add the following code in the routes section. I removed the user route since I'm not going to use it.

app.get('/', routes.index);
app.get('/login', routes.login);
app.get('/register', routes.register);

Now open the routes/index.js file so we can define these login and register methods. I'm going to redirect all root traffic to login as well since everyone needs to come through the front door. No cheating.

/*
 * GET home page.
 */

exports.index = function(req, res){
  res.redirect('login');
};

/*
 * GET login page.
 */

exports.login = function(req, res) {
  res.render('login');
};

/*
 * GET register page.
 */

exports.register = function(req, res) {
  res.render('register');
};

Now visiting the application at http:localhost:3000 should take you to the login screen where you can navigate to the register page, and then back again.

Validating Registration

Before we can log a user in, we have to be able to register one. Before we can register the user, we have to be able to validate that we have enough information to do so.

I am going to use Kendo UI to perform client-side validation on the registration form. Once I add it in, our registration page looks - well - more verbose.

doctype 5
html
  head
    title Dashboard :: Register
    link(rel='stylesheet', href='//cdn.kendostatic.com/2013.3.1119/styles/kendo.common.min.css')
    link(rel='stylesheet', href='//cdn.kendostatic.com/2013.3.1119/styles/kendo.bootstrap.min.css')
    link(rel='stylesheet', href='//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css')
    link(rel='stylesheet', href='stylesheets/style.css')
  body
    .login.well
      h1  Welcome aboard!
      hr
      form(id='register-form', method='POST', action='/register' novalidate)
        .form-group
          label(for='display') Display Name
          input(type='text', name='display', class='form-control', 
            id='display', placeholder='Enter display name', 
            required, data-required-msg='Display Name is required')
        .form-group
          label(for='username') Username
          input(type='text', name='username', class='form-control', 
            id='username', placeholder='Enter username', 
            required, data-required-msg='Username is required')
        .form-group
          label(for='password') Password
          input(type='password', name='password', class='form-control', 
            id='password', placeholder='Enter password'
            required, data-required-msg='Password is required')
        .form-group
          label(for='confirm-password') Confirm Password
          input(type='password', name='confirm-password', class='form-control', 
            id='confirm-password', placeholder='Confirm password'
            required, data-required-msg="Please confirm your password")
        .form-group
          label(for='email') Email
          input(type='email', name='email', class='form-control', 
            id='email', placeholder='Enter email',
            required data-required-msg='Email address is required')    
        .form-group
          label(for='confirm-email') Confirm Email
          input(type='email', name='confirm-email', class='form-control', 
            id='confirm-email', placeholder='Confirm email',
            required data-required-msg='Please confirm your email address')
        .form-group
          button.btn.btn-default Register
          a(href="login") Or Login
        include messages

  script(src='//code.jquery.com/jquery-1.9.1.min.js')
  script(src='//cdn.kendostatic.com/2013.3.1119/js/kendo.all.min.js')

  script

    $(function() {

      // cache references to input controls
      var password = $('#password');
      var confirmPassword = $('#confirm-password');
      var email = $('#email');
      var confirmEmail = $('#confirm-email');

      $("#register-form").kendoValidator({

        rules: {

          passwordMatch: function(input) {

            // passwords must match

            if (input.is('#confirm-password')) {
              return $.trim(password.val()) === $.trim(confirmPassword.val());
            }

            return true;

          },

          emailMatch: function(input) {

            // email addresses must match

            if (input.is('#confirm-email')) {
              return $.trim(email.val()) === $.trim(confirmEmail.val());
            }

            return true;

          }
        },
        messages: {

          // custom error messages. email gets picked up 
          // automatically for any inputs of that type

          passwordMatch: 'The passwords don\'t match',
          emailMatch: 'The email addresses don\'t match',
          email: 'That doesn\'t appear to be a valid email address'

        }

      }).data('kendoValidator');

    });


However, this form is rock solid in that Kendo UI will not allow the form to be submitted without all the proper values.

This forms posts to the register URL, so add a route for that in app.js.

// GET
app.get('/', routes.index);
app.get('/login', routes.login);
app.get('/register', routes.register);

// POST
app.post('/register', routes.registerUser);

Now create the regiserUser method in routes/index.js. We can pull the form values right off of the request body. Everlive will want at least a username and password, but will take any additional fields like Email and DisplayName.

/*
 * POST register user.
 */

exports.registerUser = function(req, res) {

  // pull the form variables off the request body
  var username = req.body.username;
  var password = req.body.password;

  // additional registration information
  var additional = {
    Email: req.body.email,
    DisplayName: req.body.display
  };

  // register the user...

WAIT. The client validated the input, but we can never fully rely on that. This is one of the truly maddening things about web development. We send our data to another place and time that we have no control over, so we can't be sure that what we get back has any integrity at all.

Node has a validator package that is exposed as "Express Middleware" called "Express Validator". Install it with NPM.

> npm install express-validator --save-dev

Then require it at the top of the app.js file.

var express = require('express')
  , routes = require('./routes')
  , http = require('http')
  , path = require('path')
  , validator = require('express-validator');

"Middleware" simply means that we can use this library directly on the request and response HTTP objects in Express since this library is tied into the Express core.

Once the data has been run through the validator, we check for errors and then pass them back to the client if there are any. Just for testing, I'm going to pass back a success message that we'll eventually replace with the actual register functionality.

exports.registerUser = function(req, res) {

  // validate the input
  req.checkBody('username', 'Username is required').notEmpty();
  req.checkBody('password', 'Password is required').notEmpty();
  req.checkBody('display', 'DisplayName is required').notEmpty();
  req.checkBody('email', 'Email is required').notEmpty();
  req.checkBody('email', 'Email does not appear to be valid').isEmail();

  // check the validation object for errors
  var errors = req.validationErrors();

  console.log(errors);  

  if (errors) {
    res.render('register', { flash: { type: 'alert-danger', messages: errors }});
  }
  else {
    res.render('register', { flash: { type: 'alert-success', messages: [ { msg: 'No errors!' }]}});
  }

};

I then create a messages.jade template which I can include in both the login and register pages which will display any messages we pass back from the server.

if flash
  div.alert(class='#{flash.type}')
    ul
    - for (var i = 0; i < flash.messages.length; i++) {
        li #{flash.messages[i].msg}
    - }
    ul

Then just include this template wherever you want the messages to show up in the login and registration pages.

include messages

 

Registering Users In Everlive

We are finally ready to register users! I know it seems like a lot of code, but this is the reality of forms. This is why this is such a great exercise for testing and learning Express.

Everlive exposes it's API via REST URL, but there is also a JavaScript SDK that is exposed in the form of both a browser script and an npm package.

Install the Everlive SDK with NPM.

> npm install everlive-sdk --save-dev

In case you are wondering what --save-dev is for, we are using it to store our dependency on these new packages in the project package.json file. This way, you can have your mates pull the project down and all they have to do is the standard npm install when they set the project up. They will love and cherish you for this.

Grab your API Key from the Everlive project you created.

 

Include the Everlive SDK at the top of the routes/index.js file and then initiliaze the library, passing in your API key. I also added a flash variable at the top of the file that I can use to pass messages from other methods to a view.

var Everlive = require('everlive-sdk');
var key = "vtp.....";

var el = new Everlive(key);

The Everlive SDK wraps all of the REST API configuration into some neat methods, and the best part is that they ALL return promises since they are asynchronous calls to a remote service.

Register the user calling the register method on the Users object. If the registration fails, set the flash message and return the register method. If it's successful, return the login page and flash a message that they should confirm their account.

var Everlive = require('everlive-sdk');
var key = "vt.........";
var flash = {};

var el = new Everlive(key);

/*
 * GET home page.
 */

exports.index = function(req, res){
  res.redirect('login');
};

/*
 * GET login page.
 */

exports.login = function(req, res) {
  res.render('login', { flash: flash });
};

/*
 * GET register page.
 */

exports.register = function(req, res) {
  res.render('register', { flash: flash });
};

/*
 * POST register user.
 */

exports.registerUser = function(req, res) {

  // validate the input
  req.checkBody('username', 'Username is required').notEmpty();
  req.checkBody('password', 'Password is required').notEmpty();
  req.checkBody('display', 'DisplayName is required').notEmpty();
  req.checkBody('email', 'Email is required').notEmpty();
  req.checkBody('email', 'Email does not appear to be valid').isEmail();

  // check the validation object for errors
  var errors = req.validationErrors();

  if (errors) {

    flash = { type: 'alert-danger', messages: errors };
    res.redirect('register');

  } else {

    // pull the form variables off the request body
    var username = req.body.username;
    var password = req.body.password;
    var additional = {
      Email: req.body.email,
      DisplayName: req.body.display
    };

    // register the user with everlive
    el.Users.register(username, password, additional).then(function() {

      // success

      console.log('success');

      flash.type = 'alert-success';
      flash.messages = [{ msg: 'Please check your email to verify your registration. Then you will be ready to log in!' }];

      res.render('login', { flash: flash });

    }, function(error){

      // failure

      console.log(error);

      flash.type = 'alert-danger';
      flash.messages = [{ msg: error.message }];

      res.render('register', { flash: flash });

    });
  }
};

Now you can register a user with your Everlive back-end. If you fill out all the fields right, you are redirected to the login page and told to verify your email address.

 

Just for fun, go back and try to register with the same username again.

 

That's Everlive handling all of the account registration validation. Usernames are unique and so are email addresses.

Check Your Email!

Speaking of emails, Everlive should have just sent you an email when you registered your user. The email includes a link to verify your account. Don't click it just yet.

Everlive allows you to customize the email messages it sends, but not with the Developer Account

Let's implement the login functionality. Remember the login form? We created it at the very beginning of this article, and now we need it. Add the flash message template to the login.jade code. We also need to validate it. Fortunately, that doesn't require nearly as much code as the registration page.

doctype 5
html
  head
    title Dashboard :: Login
    link(rel='stylesheet', href='//cdn.kendostatic.com/2013.3.1119/styles/kendo.common.min.css')
    link(rel='stylesheet', href='//cdn.kendostatic.com/2013.3.1119/styles/kendo.bootstrap.min.css')
    link(rel='stylesheet', href='//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css')
    link(rel='stylesheet', href='stylesheets/style.css')
  body
    .login.well
      h1  Please log in...
      hr
      form(id='login-form', method='POST', action='/login' novalidate)
        .form-group
          label(for='username') Username
          input(type='text', name='username', class='form-control', 
            id='username', placeholder='Enter username',
            required, data-required-msg='Username is required')
        .form-group
          label(for='password') Password
          input(type='password', name='password', class='form-control', 
            id='password', placeholder='Enter password',
            required, data-required-msg='Password is required')
        .form-group
          button.btn.btn-default Login  
          a(href="register") Or Register
        include messages

  script(src='//code.jquery.com/jquery-1.9.1.min.js')
  script(src='//cdn.kendostatic.com/2013.3.1119/js/kendo.all.min.js')

  script

    $(function() {

      // validate the login form before submit
      $("#login-form").kendoValidator();

    });

This form does an HTTP POST to the 'login' URL. By now, you should be getting the hang of what we have to do now.

Create the post method route in the app.js file.

// GET
app.get('/', routes.index);
app.get('/register', routes.register);
app.get('/login', routes.login);

// POST
app.post('/register', routes.registerUser);
app.post('/login', routes.loginUser);

Now create the loginUser method in the routes/index.js file which calls the login method on the Everlive Users object.

exports.loginUser = function(req, res) {

  // pull the form variables off the request body
  var username = req.body.username;
  var password = req.body.password;

  // register the user with everlive
  el.Users.login(username, password).then(function(data) {

    // success

    res.render('dashboard')

  }, function(error){

    // failure

    locals.type = 'error';
    locals.message = error.message;

    res.render('login', { flash: flash });

  });
};

The Dashboard view is the holy grail. It is the ENTIRE reason we have gone to all this trouble to register and login users. Go ahead and create it. I've added a simple Bootstrap navbar which will display the user's name and a logout button.

Storing The Secure Login

Right now, we just blindly log people in since the login method for Everlive just returns a success or failure. Once we get a success, we can call the currentUser() method to get information about the user. Then we can store it securely on the server for the life of the session using...

Express Sessions

Sessions are secure data that is isolated to a specific user during a specific window of application use by way of cookies. Express includes session support in it's out-of-the-box Middleware package. We can add it in app.js

app.configure(function(){
  app.set('port', process.env.PORT || 3000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.favicon());
  app.use(express.logger('dev'));
  app.use(validator([]));
  app.use(express.bodyParser());
  app.use(express.methodOverride());

  // add session support!
  app.use(express.cookieParser());
  app.use(express.session({ secret: 'sauce' }));

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

Now modify the login method in routes/index.js to check that the user verified their account and then store the user's information securely in the session.

exports.loginUser = function(req, res) {

  // pull the form variables off the request body
  var username = req.body.username;
  var password = req.body.password;

  // register the user with everlive
  el.Users.login(username, password).then(function(data) {

    // success

    req.session.authenticated = true;

    el.Users.currentUser().then(function(data) {

      // success

      // only log this user in if they have verified their account
      if (data.result.IsVerified) {

        req.session.user = data.result;
        res.render('dashboard');

      } else {

        flash.type = 'alert-danger';
        flash.messages = [{ msg: 'You have registered, but have not yet verified your account.  Please check your email for registration confirmation and click on the provided link to verify your account.' }];

        res.render('login', { flash: flash });

      }

    }, function(error) {

      // failure

      flash.type = 'error';
      flash.messages = [{ msg: error.message }];

      res.render('login', { flash: flash });

    });

  }, function(error){

    // failure

    flash.type = 'error';
    flash.messages = [{ msg: error.message }];

    res.render('login', { flash: flash });

  });
};

We don't have to validate this time because Everlive isn't going to let us get very far without a username and password. Also, the user can't login until they have verified their account.

Once the user has verified their account, they can login and access the sacred Dashboard page. Actually, they can access it without logging in because we haven't restricted access to it yet.

Restricting Access To Routes

Did I mention that Express is very minimalistic? We kind of need to implement our own function to enforce security. The good news is that it's incredibly easy since we can pass a second function to a route which intercepts the request and response and allows us to perform some function on them.

function restrict(req, res, next) {

  // if the user is authenticated...
  if (req.session.authenticated) {
    // PROCEEED
    next();
  } else {
    // not authenticated - go to the back of the line
    res.redirect('/login');
  }
}

// GET
app.get('/', routes.index);
app.get('/register', routes.register);
app.get('/login', routes.login);

// restrict the dashboard route
app.get('/dashboard', restrict, routes.dashboard);

// POST
app.post('/register', routes.registerUser);
app.post('/login', routes.loginUser);

Go ahead and try it out! I've hidden some great content for you behind this login page, but you're going to have to register to see it.

The full source code for this project is up on GitHub.

Express Is Fun!

It is! It's a no-frills framework, and I think that's a GOOD thing. I like it. Having an intelligent backend system with a nice SDK really helps as well. So turn up that "Arcade Fire" album, put on those Buddy Holly glasses and embrace your inner hipster.


Burke Holland is the Director of Developer Relations at Telerik
About the Author

Burke Holland

Burke Holland is a web developer living in Nashville, TN and the Director of Developer Relations at Telerik. He enjoys working with and meeting developers who are building mobile apps with jQuery / HTML5 and loves to hack on social API's. Burke works for Telerik as a Developer Advocate focusing on Kendo UI.

Related Posts

Comments

Comments are disabled in preview mode.