Saturday 26 March 2011

Setting up a simple login form with node.js

Having spend the majority of the night setting this supposedly "simple" thing up, I'll give you guys the tutorial I needed.

Let's start of with the code. Here's the github link . To get it to work your going to have to install a bunch of things through npm.

In case you've never used npm before then go get it.

I personally wanted to set up a good foundation for this rather then get something quick and dirty out so I went for installing express. If you haven't already done this then go over to the express site and watch the screencasts. It gives you a good overview of what express is. 

To start my project I created a fresh express project with

$ express --sessions --css less

This set the express project up with the basic app file, jade templating and less for CSS compilation. Now I agree all of these things are completely over kill for something simple, but if you want to expand and grow on something it's best to have your foundation as large as possible.

The templates : 


index.jade


!= partial('login')
div#data.hidden

If you've seen the screencasts (Make sure to watch them) You can see that our HTML is just a call to our partial view for the login and a hidden data div to be shown when the login is successful.

login.jade

div#logincontainer
    h1 Please login

    label(for='username') Username:
    br
    input#username
    br
    label(for='password') Password:
    br
    input(type='password')#password
    br
    p#error.hidden
    br
    input(type='button', value='Login')#login.button
    input(type='button', value='Register')#register.button
We should be able to see what this is. We have a div container with some labels, inputs and buttons to login in or register. I won't bother to show the LESS but you can look at the github source if your interested.

I should point out that I've added this one line to layout.jade to kick start the client side javascript using require.js

script(type='text/javascript', src='/javascripts/lib/require.js', data-main='/javascripts/main')

require.js is pretty neat in this regard. You simply include the script and set a data-main attribute which will be loaded by require when require itself is ready. This is a really clean way to include javascript in your templates in one line.

Server side js in app.js :

In my own code I've left the auto generated routes code almost untouched.

Routes:

// Routes

app.get('/', function(req, res){
    res.render('index', {
        title: 'Pong Game'
    });
});

To be honest I did actually expect to make a pong game over the night but the learning curve did delay me quite considerably. To re-iterate what the express guys say this simply handles get requests on the main route and gets express to render our index view with the title local passed to it.

Next up is setting up the mongoose, mongo db connection. For this to even remotely work I recommend you download and install mongo db and if your own unix step through the mongo db unix tutorial

Mongoose:

// Mongoose db

    var mongoose = require("mongoose");

    mongoose.connect("mongodb://localhost/ponggame");

    var userSchema = new mongoose.Schema({
        'username': String,
        'password': String
    });
    
    userSchema.pre("save", function(next) {
        var user = this.username, password = this.password;
        if (user.length < 4) {
            next(new Error("User too short"));
        } else if (password.length < 8) {
            next(new Error("Password too short"));
        }
        next();
    });

    mongoose.model('User', userSchema);

    var User = mongoose.model("User");

First we get mongoose to connect to a database with an url and a database name. In this case the url is localhost and the database is ponggame. (I will actually get round to writing a multi player pong game.).

The way mongoose works is that you create schema's and interact through them. So I made a very simple userSchema that has a user name and password string (passwords in plain text, the best security ever). I'll leave implementing real security as a exercise for the user. I was going to try but I don't have any HTTPS certs lying around.

You can use the same concept of middle ware as you would in express, in mongoose. So I've added some validation middle ware to the save method. Ideally you would use the in build validation system but I wanted to test pre. In your middle ware method you can access the particular instance of the schema through this and you just simply do what you want then either call next. Here you can pass through an error as the first parameter and your callback on the save method can check for errors like that.

Of course we need to tell mongoose that this schema is a model and then we also need to ask mongoose for a real model object from our schema.

Of course we haven't actually done anything with our database yet but we're only going to interact with it on our communication layer.

Now:

// Now communication

    var everyone = require("now").initialize(app);

    function findUser(data, cb) {
        var user = {};
        if (data.username !== undefined) {
            user.username = data.username;
        }
        if (data.password !== undefined) {
            user.password = data.password;
        }
        User.find(user).exec(cb);
    }

    everyone.now.login = function (data) {
        var that = this;
        findUser(data, function(err, arr) {
            if (err) {
                that.now.loginFailure(err);
            } else if (arr && arr.length > 0) {
                that.now.loginSuccess(arr[0].doc);
            } else {
                that.now.loginFailure();
            }
        });
    };

    everyone.now.register = function (data) {
        var that = this;
        var user = {
            "username": data.username
        };
        function handleUsersFound(err, arr) {
            console.log(arguments);
            if (err) {
                that.now.registerFailure(err);
            } else if (arr && arr.length > 0) {
                that.now.registerFailure("User is already taken");
            } else {
                var user = new User();
                user.username = data.username;
                user.password = data.password;
                var o = user.save(handleSave);
            }
        }
        function handleSave(err) {
            console.log("save handled");
            if (err) {
                that.now.registerFailure(err);
            } else {
                that.now.registerSuccess();
            }
        }
        findUser(user, handleUsersFound);
    }

Hey look here, We're making all the communication trivial with now. We just have to define some methods on the server and we can call them on the client. Where possible the client will use real web sockets to communicate over TCP with the server.

To start of we initialize the everyone variable with the instance of our server (which is our express app).

I've defined a helper function which takes which tries to find a user in our database. it creates an object literal with a user name and optional password then calls .find on our model to find all users who match the name (and optionally the password). We then call .exec with a callback on our query to run it.

Calling find on the model is building a query and calling exec runs it. The callback takes two parameters, an error parameter and an array of data found in our Model.

Our server side login method is easier to understand we try to find the user with the data passed in from the client. In our callback we check for errors and pass them to the client if they exist. Calling methods that exist on "that.now" is simply calling method defined on the particular client that called the server method. If the arr exist and is non empty we tell the client his login succeeded and if the array is empty well then the user/password combination didn't exist and we just tell the user that the login failed.

Our register method is similar but a bit more complex. we again find the user but this time we only search for the user name. If the query returns a non-empty array then the user already exist and we throw conflict error. Otherwise we create a new user from the model. Set it's values and save it.

We need to have a callback on the save to see whether it was successful. We have some middle ware on the save that does validation and will pass in an error if the validation failed. So if the validation failed tell the client his register failed otherwise tell him he registered successfully.

Client side js in main.js :


Validation and talking to server :

(function() {

    require(["javascripts/lib/jquery.js", "/nowjs/now.js"], function() {
        $(function() {
            var login = $("#login.button"),
                register = $("#register.button"),
                username = $("#username"),
                password = $("#password");

            function validate(user, pwd) {
                if (user.length < 4) {
                    $("p#error").text("User too short").show();
                    return false;
                } else if (pwd.length < 8) {
                    $("p#error").text("Password too short").show();
                    return false;
                }
                return true;
            }

            $.each({"login": login, "register": register}, function(key, val) {
                val.click(function() {
                    var user = username.val();
                    var pwd = password.val();
                    if (validate(user, pwd)) {
                        $("p#error").hide();
                        now[key]({
                            "username": user,
                            "password": pwd
                        });
                    }
                });
            });
        })
    });

}());

So this is the main.js file that require.js loads for us. I have a habit of wrapping everything in its own private function scope so I use a self executing function for that.

Here we ask require to make sure that jQuery and now are loaded (Make sure you npm installed now or that file location won't work) before we do our code execution.

The validate function here looks similar to the one on the server. If the validation was larger/more complex I would be tempted to use now to run the validation from the server on the client. But currently the validation simply checks that the user and password are a sensible length and if there not they set the error text and show it.

The click handlers for login and register were so similar I used $.each to run the same function for both, the only thing that changes is which jQuery object to bind the click function to and which server side function to call on the now object. The click handler simply grabs the user name and password then runs validation and passes this data to the login or register function on the server.

The beauty here is that the entire socket.io communication layer is handled for you under the hood. Be wary of efficiency though. It's best to only define functions and use callbacks with now. Keeping data synchronized is not cheap. Nesting objects in the now name space is not cheap either.

In this particular instance I hide the error message if the validation succeeds. it doesn't feel right to put it there but I haven't set up backbone to handle the views for the login form on the client yet. (That's really overkill).

Methods called when server talks to client : 

// client side callbacks.

            now.registerSuccess = function success() {
                console.log("register sucess");
                $("p#error").text("").hide();
                $("div#logincontainer").hide();
                $("div#data").text("You registered").show();
                setTimeout(function() {
                    $("div#data").text("").hide();
                    $("div#logincontainer").show();
                }, 5000)
            };

            now.registerFailure = function fail(msg) {
                console.log("register failure");
                msg || (msg = "");
                $("p#error").text("Register failed " + msg.toString()).show();
            };

            now.loginSuccess = function success() {
                console.log("log in success");
                $("p#error").text("").hide();
                $("div#logincontainer").hide();
                $("div#data").text("You logged in").show();
            };
            now.loginFailure = function fail(msg) {
                console.log("log in failure");
                msg || (msg = "");
                $("p#error").text("Log in failed " + msg.toString()).show();
            };


Here are the four call backs that the server calls inside it's login/register method to tell the client that it either succeeded or failed. These simply manipulate the DOM pleasantly.

The register success hides errors, the login screen and shows a message in our data div saying you've registered. There's a timeout to reshow the login screen so the user can login in with its registered user.

The login success hides errors, the login screen and shows a message saying you've logged in.

The failure functions sets the msg to it's default value if it's falsy and shows the error.

This really isn't very DRY at all, these could easily have been replaced with a single success / failure function which takes an argument saying what failed/succeeded.

Starting the server with supervisor : 



If you've also installed supervisor then you can use the command line to start your app like so :

$ supervisor app.js

This means that supervisor will restart your app if it crashes. It also does hot reloads on any code changes so you don't have to turn your server on/off manually each time you make a change.

That's all for now. I hope this helps you with a few of the basics of express/mongoose and now.