Introduction

So apart from a 2am A&E trip and a severely sprained ankle, this week at Makers Academy has been a lot of fun. Set the challenge of building an AirBNB clone in small groups, we decided the difficulty of working in groups on a new project for the first time wasn’t enough. Therefore, with much aplomb, we set out to complete the project using full-stack Javascript. The complete stack we ended up using was:

Backend:
- Bookshelf/Knex
- knexCleaner
- Node.JS
- PostgreSQL

Frontend:
- JQuery
- Embedded Javascript
- Bootstrap CSS

Testing:
- Jasmine-Node
- Zombie

As we only learnt Javascript last week, this presented the sort of challenge we were looking for, having used none of the backend or testing frameworks before. But it wasn’t quite enough for us, so as well as learning how to use Node, we decided not to use express. ExpressJs is a framework for node, that provides a myriad of utility helper methods and shortcuts to make node easier. This sounded a little like cheating so we decided to use core node only.

For the rest of the post we’ll look at some example code to give a flavor of our final solution.

A note on asynchronicity

Before we begin, we need to understand an intrinsic point of difference between Node and Sinatra. Node is a single threaded asynchronous engine. This means that it generally will only execute one thing at a time, and will move on to the next command without waiting for the first to finish executing.

Imagine a restaurant, where your waiter submits your order to the cook, then sits down and twiddles his/her thumbs while the chef cooks, then when the food is ready delivers it to your table. This is a synchronous operation. It may be the best thing for you the customer, but it’s inefficient for the owner. To be able to have multiple customers he needs to have multiple waiters.

Javascript on the other hand is single threaded, but asynchronous. There is only one waiter, but he doesn’t have to wait for other processes to finish before he can move on to the next. He gives the order to the chef, then goes and takes another order. So that he knows to go pick up the food when its ready, we provide him with a callback function, which is called when the process he waits on is done.

The difference in needed computing power for these two paradigms is dramatic. In a language like ruby, assuming that each thread potentially has an accompanying 2 MB of memory with it, running on a system with 8 GB of RAM puts us at a theoretical maximum of 4000 concurrent connections

By using asynchronicity, Node.js achieves scalability levels of over 1M concurrent connections. This translates into real savings in terms of the server resources needed to run your application.

Jasmine-Node and Zombie

Being first class TDD-ers lets look at the testing frameworks first. We used a combination of Jasmine-Node and Zombie for our testing. Zombie is a headless browser, which allows us to write tests that explore our website in a browser, without having to have an open browser window.

Once properly integrated with jasmine this means we can write tests that visit pages, fill in forms, and click buttons. Lets look at an example of a feature test for logging into our account:

describe('Login testing', function(){

  beforeEach(function(){
    server.listen(3000);
    browser.deleteCookies();
    knex('users').insert({
      email: 'rosie@allott.com', password: '$2a$10$MjmF1z/VeNe7V5asctIbDOyM8fJeqGeMYFUni7V5Xt80QL5hGCn8G'
    });
  });

  afterEach(function(){
    server.close();
    knexCleaner.clean(knex);
  });

  describe("users/login", function(){
    it("should be able to log in", function(next){
      browser.visit(url + '/users/login', function(err){
        browser.fill('#email-address', 'rosie@allott.com');
        browser.fill('#password', 'password');
        browser.pressButton('#login', function(){
          expect(browser.html("body")).toContain("Successfully logged in");
          next();
        });
      });
    });
  });
});

We start with a beforeEach block which runs before each test. In this block we start a new server, delete all cookies saved in the browser, and insert a test user into our database. The password are saved salted hash for security, and so we insert a known hash (this one equals ‘password’).

Our afterEach block performs the reverse operation, we close our local server, and delete all entries from our database using knexCleaner.

Finally we get to our first test. This test:
1. Visits the ‘/users/login’ path
2. Fills in the form on that page with the details that match those in our database
3. Clicks the login buttons
4. Asserts that the new page, has the text - ‘Successfully logged in’

While this assertion may not be the most full proof way to check the login has been successful lets run with it for now.

NodeJs

So onto the main event. Below I’ve put a section of the node controller that deals with out login procedure.

this.server = http.createServer(function (req, res){
  session.startSession(req, res, function(){

    if (req.url == '/users/login' && req.method == 'GET') {
      fs.readFile('./views/users/login.ejs', 'UTF-8', function(err, page){
        var html = ejs.render(page, {message: req.session.get('message')});
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write(html);
        res.end();
      });
    }

    else if (req.url == '/users/login' && req.method == 'POST') {
      var body = '';
      req.on('data', function(data){
        body += data;
      });

      req.on('end', function(){
        var params = qs.parse(body);
        User.where({email: params.email}).fetch().then(function(user){

        if (user && bcrypt.compareSync(params.password, user.toJSON().password)) {
          req.session.put('id', user.toJSON().id);
          req.session.flash('message', 'Successfuly logged in');
          res.writeHead(302, {Location: '/spaces'});
          res.end();
        }
        else {
          req.session.flash('message', 'Incorrect email or password');
          res.writeHead(302, {Location: '/users/login'});
          res.end();
        }
        });
      });
    }
  });
});

This code can be broken down into a few key sections:

  1. We create a new HTTPServer using http.createServer(){}. This takes two arguments, a request and a response.
  2. We create a new session using session.startSession(){}, which allows us to persist information between requests.
  3. We then have a series of if statements. Each of these cover a different route.
  4. In each of these routes we write a response, and send it off to the client.

The first route covers a GET request to the /users/login route. We load the login.ejs view, then create a response header with a 200 status code, and Content-Type. Then we insert the html into the body, and then close the response.

The second route is slightly more involved, and is the path that’s followed when a user signs in.

The first few lines are activated when a request sends data. This saves the body of the post request into our body variable.

When the request ends, we parse the body into a JSON object. From this we look up in our database the user, with the email the user enters.

Once we have a response from the database, we check that the user exists (i.e. if the email matches a known user, and that the password hash matches the password the user entered).

If both these checks pass, we add the id of the user into a session variable, along with a success message. We then create a response with a 302 code. This is a redirect response, which pushes the user to the given location.

If one of the checks fails, we redirect back to the login page, and pass a error message to the user.

It’s important to understand how the asynchronicity impacts how we write these routes. We have multiple embedded callbacks, which are called when the associated function has been completed.

Express handles a lot of the idiosyncrasies of node for you. Things like having to aggregate and parse the post request data are handled by a plentitude of built in helper methods. But the advantage of having learnt node this way is that it gives you an insight into what is happening under the hood. I found it to be a really worthwhile exercise and something I recommend any Node user do.

That being said it became obvious early on in our project that Node was a poor choice for this sort of database read/write heavy web application. Our purposes would have been served much better using a synchronous solution, like Sinatra or Rails instead of trying to shoe-horn Node to our purposes. However, we set out to learn about Node, and for that it was an excellent experience.

Updated:

Leave a Comment