9. Databases II: SQL (MySQL)

Although the NoSQL databases are surging in popularity, there are still plenty of good reasons to continue using relational databases, and they too remain just as popular as ever, especially the two most common open source variants, MySQL and PostgreSQL. The good news is that the asynchronous nature of Node.js meshes perfectly well with using these databases in your web applications, and there is good npm module support for most of them.

In this chapter, I cover using MySQL with Node, using the mysql module from npm. Because you learned how to put your albums and photos in the database in the preceding chapter, you can now turn your focus to registering users in the application and requiring them to be logged in before creating any albums or adding any photos. We leave seeing how albums and users were converted to MySQL as an exercise for the reader to view in the GitHub source code.

Even if you’re not planning on using traditional relational databases such as MySQL, it is still worth your while to read through this chapter because I introduce a couple of important features in express, as well as talk about resource pooling, a way to control and limit the use of precious system resources. You also update the photo albums sample so that albums and photos will work with MySQL.

Getting Ready

You need to do two things before you can start playing around with MySQL in Node.js: make sure MySQL is installed and install the mysql npm module.

Installing MySQL

If you haven’t installed MySQL on your development machine yet, visit dev.mysql.com/downloads/mysql and download the version of the community server most appropriate for your machine. For Windows and Mac OS X, you can find downloadable installers, whereas for Linux and other UNIX systems, you can unpack .tar.gz archives to your preferred location (most typically /usr/local/mysql).

If you’re running the installers for Windows or Mac OS X, they take care of everything for you, whereas for binary distributions only, you have to read the INSTALL-BINARY text file and follow the instructions in there to get MySQL fully up and running. When you’re done, you should be able to start a new command prompt or terminal and run the mysql command:

hostname:Learning Node marcw$ /usr/local/mysql/bin/mysql -u root
Welcome to the MySQL monitor. Commands end with ; or g.
Your MySQL connection id is 1
Server version: 5.7.14 MySQL Community Server (GPL)

mysql>

Adding the mysql Module from npm

To install the mysql module for Node.js, you can modify package.json and add the following under dependencies:

  "dependencies": {
    "async": "2.x",
    "mysql": "2.x"
  }

You should now see mysql/ under node_modules/ in your folder. Note that the 2.x series of the mysql module is a significant improvement and departure from the 0.x series. It is much more robust than previous versions.

Creating a Schema for the Database

When working with MySQL, you need to create a database schema for the application, which you place in schema.sql. The first part of this is to create a database with UTF-8 as the default character set and sorting order, as follows:

DROP DATABASE IF EXISTS PhotoAlbums;

CREATE DATABASE PhotoAlbums
    DEFAULT CHARACTER SET utf8
    DEFAULT COLLATE utf8_general_ci;

USE PhotoAlbums;

You then have to create a schema for your Users table, where you place information for registered users of your photo-sharing app. You only need to require an email address, display name, and password from the user and to store a couple of extra pieces of information, including when the account was created, when it was last modified, and whether or not it’s been marked as deleted. The Users table looks as follows:

CREATE TABLE Users
(
  user_uuid VARCHAR(50) UNIQUE PRIMARY KEY,
  email_address VARCHAR(150) UNIQUE,

  display_name VARCHAR(100) NOT NULL,
  password VARCHAR(100),

  first_seen_date BIGINT,
  last_modified_date BIGINT,
  deleted BOOL DEFAULT false,

  INDEX(email_address),
  INDEX(user_uuid)
)
ENGINE = InnoDB;

Running all these commands in MySQL (mysql -u user -p secret < schema.sql) sets up the appropriate database and table structures you need to begin writing code. If you’re following along with the code from the GitHub repository, you’ll notice that tables have been added for albums and photos as well to keep our app working!

Basic Database Operations

Most of your work with MySQL will be limited to making connections to the MySQL server and executing these queries and statements. This gives you more than enough to run your straightforward web applications and extract much of the power of the database server.

Connecting

To connect to the remote server, you create a connection via the mysql module and then call the connect function, as follows:

    conn_props = local.config.db_config;
    client = mysql.createClient({
        host:            conn_props.host,
        user:            conn_props.user,
        password:        conn_props.password,
        database:        conn_props.database
    });
};

You might have noticed that this is a bit different from the init method we had in the db.js file in the previous chapter for MongoDB. It turns out that MySQL manages its own connections and connects and disconnections whenever it needs to, so you don’t need to do the initialization that you did for MongoDB.

If you’ve passed in bad values or other invalid information, you get an error. If the connection succeeds, you can then use your client object to execute queries with the query method. After finishing all your work with the database, you should close the connection by calling the end method:

dbclient.end();

There is one problem with this code, however—it creates only a single connection. If we have dozens of requests coming in at the same time, attempting to execute database queries for them using the same connection will be extremely slow and cause problems. To get around this, the mysql module now includes connection pooling (it used to be that you had to use another module instead). Conveniently, we just have to change the method we use to create the dbclient, as follows:

    conn_props = local.config.db_config;
    dbpool = mysql.createPool({
        connectionLimit: conn_props.pooled_connections,
        host:            conn_props.host,
        user:            conn_props.user,
        password:        conn_props.password,
        database:        conn_props.database
    });

The createPool method means that whenever we call the query method, the dbclient will look for an existing connection to the database and reuse it. If none exists, it’ll create a new one up to connectionLimit times. If there are no connections left, it’ll pause your query until a connection comes free and then execute it. All we have to do in our code is execute the queries, and everything is taken care of for us—we don’t have to worry about calling the end method or anything else.

Adding Queries

After connecting to the database server, you can start executing queries with the query method, to which you pass the SQL statements you want executed:

dbclient.query("SELECT * FROM Albums ORDER BY date",
               function (err, results, fields) {});

If the query succeeds, the results parameter passed to the callback contains data relevant to what you requested, and there is a third parameter, fields, for those cases when you have additional information to specify (it is often empty when you don’t have such information). For SELECT statements, the second parameter is an array of queried rows:

dbpool.query("SELECT * FROM Albums", function (err, rows) {
    for (var i = 0; i < rows.length; i++) {
        console.log(" -> Album: " + rows[i].name
                    + " (" + rows[i].date + ")");
    }
});

If you are using INSERT, UPDATE, or DELETE (to add, change, or remove rows from the specified table correspondingly), the results you get back from the query method look more like the following:

{ fieldCount: 0,
  affectedRows: 0,
  insertId: 0,
  serverStatus: 2,
  warningCount: 0,
  message: '',
  changedRows: 0 }

You can use this information to make sure that the number of affected rows is what you expected it to be by looking at the affectedRows property, or you can get the autogenerated ID of the last inserted row via the insertId property.

To specify values in your MySQL queries, you use placeholders, which are indicated with the ? character, as follows:

dbpool.query("INSERT INTO Albums VALUES (?, ?, ?, ?)",
             [ "italy2012",
               "Marc visits Italy",
               "Marc spent a month in Italy!",
               "2012-01-01" ],
             callback);
dbpool.query("SELECT * FROM Albums WHERE albumid = ? ORDER BY album_date DESC",
             [ "australia2010" ],
             callback);

The question mark character indicates that the value will be filled in from an array passed as the second parameter to the query method, where the nth element in the array replaces the nth ? character in the query. Most importantly, these values are escaped or sanitized for you so that it becomes much harder to fall victim to SQL Injection attacks or other ways where malicious data and damage your database.

And that’s pretty much all there is to using MySQL in Node.js. Let’s put it to some serious use then!

Updating the Photo Sharing Application

We have updated the photo sharing application we are building in this book to work with MySQL instead of MongoDB. We’ll leave looking at the differences between these two way of doing things as an exercise to the reader by looking in GitHub. The good news is that 99% of what’s new and different is in the data/ subfolder—most of the serving code and handler code doesn’t need to change at all! In this chapter, we will focus on integrating passport into our photo sharing app.

Authenticating via the Database

Back in Chapter 7, we covered adding authentication to our application using the passport module. In that sample application, however, we covered only having a hard-coded list of users and passwords, which is not particularly useful in real-world usage.

In this chapter, we’re going to add passport support to our photo sharing application, and we’re going to use the database to store user information, using the Users table we described above.

Our application will support the following user operations:

Image Let new users register for the application.

Image Require a user to be logged in before using the admin pages that allow adding photos or albums.

Updating the API to Support Users

To support a new user subsystem, you need to add a couple of new routes to the API:

app.put('/v1/users.json', user_hdlr.register);
app.post('/service/login', /* we'll see this later */);
app.get('/service/logout', /* we'll see this later */);

The first is the CRUD method (see “API Design” in Chapter 7, “Building Web Applications with Express”) for creating users through the API. The second and third are the way you support logins and authentication for the web browser version of the application. I show the implementations for these two methods later in this chapter.

Examining the Core User Data Operations

To get a connection to the database, go to the data/ folder for your application and put—in addition to a backend_helpers.js file containing some simple error-handling help—a file called db.js there. This file is shown in Listing 9.1. Effectively, it provides a function to get a connection to a MySQL database using connection information provided in the file local.config.json, as you saw in Chapter 8, “Databases I: NoSQL (MongoDB).”

Listing 9.1 db.js


var mysql = require('mysql'),
    local = require("../local.config.json");

exports.init = function () {
    conn_props = local.config.db_config;
    exports.dbpool = mysql.createPool({
        connectionLimit: conn_props.pooled_connections,
        host:            conn_props.host,
        user:            conn_props.user,
        password:        conn_props.password,
        database:        conn_props.database
    });
};

exports.dbpool = null;


Whenever we need a database connection to run a query, we can just fetch db.dbpool. With this file in place, you can start implementing the back end for the preceding new APIs in data/user.js.

Creating a User

The code to register a new user in the database is as follows:

exports.register = function (email, display_name, password, callback) {
    var userid;
    async.waterfall([
        // validate the params
        function (cb) {                                           // 1.
            if (!email || email.indexOf("@") == -1)
                cb(backend.missing_data("email"));
            else if (!display_name)
                cb(backend.missing_data("display_name"));
            else if (!password)
                cb(backend.missing_data("password"));
            else
                cb(null);
        },

        function (cb) {
            bcrypt.hash(password, 10, cb);                        // 3.
        },
        function (hash, cb) {
            userid = uuid();                                      // 4.
            db.dbpool.query(                                      // 5a.
                "INSERT INTO Users VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), NULL, 0)",
                [ userid, email, display_name, hash ],
                cb);
        },

        function (results, fields, cb) {                          // 5b.
            exports.user_by_uuid(userid, cb);
        }
    ],
    function (err, user_data) {
        if (err) {
            if (err.code
                && (err.code == 'ER_DUP_KEYNAME'
                    || err.code == 'ER_EXISTS'
                    || err.code == 'ER_DUP_ENTRY'))
                callback(backhelp.user_already_registered());
            else
                callback (err);
        } else {
            callback(null, user_data);
        }
    });
};

The code does the following:

1. It validates the incoming parameters, in particular making sure the email address is semi-valid. You could be even stricter if you wanted and require activation of an account by sending a link to that address.

2. It gets a connection to the database.

3. It hashes the password using the bcrypt module. Bcrypt is a slow method of generating passwords that makes brute-force attacks on them extremely difficult.

4. You generate a UUID for the user. You can use it later in the API to identify users. These IDs are better than simple integer user IDs because they’re harder to guess and have no obvious ordering.

5. You execute the query to register the user in the database and finally ask the database to return that just-created user back to the caller.

You use two new modules in this code: bcrypt (for password encryption) and node-uuid (to generate the GUID that you use for the user identifier). You thus update the package.json file with the following dependencies:

  "dependencies": {
    "express": "3.x",
    "async": "0.1.x",
    "mysql": "2.x",
    "bcrypt": "0.x",
    "node-uuid": "1.x"
  }
}

Also note that you store the account creation date as a BIGINT instead of a regular MySQL DATETIME field. I’ve found that because JavaScript uses time stamps everywhere for dates and times, it can be much easier to just store and manipulate them in databases as well. Fortunately, MySQL provides a few functions to help you work with these dates.

Fetching a User (by Email Address or UUID)

Now that you have a way to save users into the database, you can write the functions to bring them back. First, write a generic function to find a user based on a particular field in the database:

function user_by_field (field, value, callback) {
    async.waterfall([
        function (cb) {
            db.dbpool.query(
                "SELECT * FROM Users WHERE " + field
                    + " = ? AND deleted = false",
                [ value ],
                cb);
        },
        function (rows, fields, cb) {
            if (!rows || rows.length == 0)
                cb(backhelp.no_such_user());
            else
                cb(null, rows[0]);
        }
    ],
    callback);
}

Now, write the exported functions to fetch a user:

exports.user_by_uuid = function (uuid, callback) {
    if (!uuid)
        cb(backend.missing_data("uuid"));
    else
        user_by_field("user_uuid", uuid, callback);
};

exports.user_by_email = function (email, callback) {
    if (!email)
        cb(backend.missing_data("email"));
    else
        user_by_field("email_address", email, callback);
};

And that’s all you have to do for the data portion of user management. Everything else you do is on the front-end handlers.

Updating the Express Application for Authentication

To update our application to support authentication, we’ll add the passport module to our package.json file, as well as passport-local authentication:

    "dependencies": {
        "body-parser": "1.x",
        "cookie-parser": "1.x",
        "express": "4.x",
        "express-flash": "0.x",
        "express-session": "1.x",
        "mysql": "2.x",
        "morgan": "1.x",
        "multer": "1.x",
        "node-uuid": "1.x",
        "passport": "0.3.x",
        "passport-local": "1.x"
    }

You’ll see that we’ve also included a few other modules to support cookies, session data, uploads, and flash messages, which we covered back in Chapter 7.

At the top of our server.js file, we’re going to have to add all the modules we’ll need for passport and configuring all the modules, as follows:

var express = require('express'),
    cookieParser = require('cookie-parser'),
    session = require('express-session'),
    passport = require("passport"),
    LocalStrategy = require('passport-local').Strategy,
    bodyParser = require('body-parser'),
    flash = require('express-flash'),
    morgan = require('morgan'),
    multer = require('multer');

var db = require('./data/db.js'),
    album_hdlr = require('./handlers/albums.js'),
    page_hdlr = require('./handlers/pages.js'),
    user_hdlr = require('./handlers/users.js'),
    helpers = require('./handlers/helpers.js');

var app = express();
app.use(express.static(__dirname + "/../static"));

var session_configuration = {
    secret: 'whoopity whoopity whoop whoop',
    resave: false,
    saveUninitialized: true,
    cookie: { secure: true }
};

session_configuration.cookie.secure = false;

app.use(flash());
app.use(session(session_configuration));
app.use(cookieParser('whoopity whoopity whoop whoop'));
app.use(passport.initialize());
app.use(passport.session());

app.use(morgan('dev'));

// Parse application/x-www-form-urlencoded & JSON
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

var upload = multer({ dest: "uploads/" });

Our initialization code has grown a bit, but our application is doing quite a lot of things now.

Implementing User Authentication

Let’s look at the methods we need to implement passport authentication. First, we need to initialize the passport local strategy with a function that validates a given username and password against our approved list of users.

passport.use(new LocalStrategy(
    function(username, password, done) {
        user_hdlr.authenticate_user(username, password, (err, user) => {
            if (err && err.code == "invalid_credentials") {
                return done(null, false, {
                    message: 'Incorrect credentials.'
                });
            } else if (err) {
                return done(null, false, {
                    message: `Error ((err.code)) while authenticating`
                });
            } else {
                return done(null, user);
            }
        });
    }
));

The key new method we have here is the authenticate_user method in the handlers/users.js file (which we include and call user_hdlr). We’ll see this in the next section, “Creating the User Handler.”

We create three other authentication functions to help us out, as follows:

function alwaysAuthenticated(req, res, next) {
    if (req.isAuthenticated()) {
        next();
    } else {
        res.redirect("/pages/login");
    }
}

function pageAuthenticatedOrNot(req, res, next) {
    if ((req.params && req.params.page_name == 'admin')) {
        if (req.isAuthenticated()) {
            next();
        } else {
            res.redirect("/pages/login");
        }
    } else if (req.params && req.params.page_name == "register") {
        if (req.isAuthenticated()) {
            req.logout();
        }
        next();
    } else {
        next();
    }
}

function verifyLoggedOut(req, res, next) {
    if (req.user) {
        req.logout();
    }
    next();
}

The first two of these are middleware (see Chapter 7 if you’ve forgotten what these are) functions that allow us to verify if an incoming request has an authenticated user associated with it. For the first, it will always require an authenticated user. For the second, it’ll require authentication only if the user is trying to access an admin page. Our last function simply makes sure the user is logged out.

With these implemented, we can now implement the user handler, handlers/users.js.

Creating the User Handler

To support management of accounts, you can create a new user handler in handlers/users.js. Just as you did previously with albums and photos, you create a new User class that will help you wrap users and also let you implement a response_obj method to filter out things you don’t want to return:

function User (user_data) {
    this.uuid = user_data["user_uuid"];
    this.email_address = user_data["email_address"];
    this.display_name = user_data["display_name"];
    this.password = user_data["password"];
    this.first_seen_date = user_data["first_seen_date"];
    this.last_modified_date = user_data["last_modified_date"];
    this.deleted = user_data["deleted"];
}

User.prototype.uuid = null;
User.prototype.email_address = null;
User.prototype.display_name = null;
User.prototype.password = null;
User.prototype.first_seen_date = null;
User.prototype.last_modified_date = null;
User.prototype.deleted = false;
User.prototype.check_password = function (pw, callback) {
    bcrypt.compare(pw, this.password, callback);
};
User.prototype.response_obj = function () {
    return {
        uuid: this.uuid,
        email_address: this.email_address,
        display_name: this.display_name,
        first_seen_date: this.first_seen_date,
        last_modified_date: this.last_modified_date
    };
};

Creating a New User

Earlier in this chapter, we saw the back-end code needed to create a new user in the database. Let’s look at the front-end portion of this. It basically does the following:

1. Checks the incoming data to make sure it’s valid.

2. Creates the user account with the back end and gets the raw data back again.

3. Returns the newly created user object back to the caller.

This function looks like this:

exports.register = function (req, res) {
    async.waterfall([
        function (cb) {                                            // 1.
            var em = req.body.email_address;
            if (!em || em.indexOf("@") == -1)
                cb(helpers.invalid_email_address());
            else if (!req.body.display_name)
                cb(helpers.missing_data("display_name"));
            else if (!req.body.password)
                cb(helpers.missing_data("password"));
            else
                cb(null);
        },
        function (cb) {                                            // 2.
            user_data.register(
                req.body.email_address,
                req.body.display_name,
                req.body.password,
                cb);
        },
    ],
    function (err, user_data) {                                    // 3.
        if (err) {
            helpers.send_failure(res, helpers.http_code_for_error(err), err);
        } else {
            var u = new User(user_data);
            helpers.send_success(res, {user: u.response_obj() });
        }
    });
};

Authenticating a User

We previously saw that we needed a method called authenticate_user to allow the passport local strategy to do its thing. Here it is:

exports.authenticate_user = function (un, pw, callback) {
    var user_object;
    async.waterfall([
        function (cb) {
            user_data.user_by_display_name(un, cb);      // 1.
        },

        function (user_data, cb) {
            user_object = new User(user_data);           // 2.
            user_object.check_password(pw, cb);          // 3.
        }
    ],
    function (err, auth_ok) {
        if (!err) {
            if (auth_ok) {
                callback(null, user_object);
            } else {
                callback(helpers.error("invalid_credentials",
                    "The given username/password are invalid."));
            }
        } else {
            callback(err);
        }
    });
};

This code basically does the following:

1. Fetches the user object for the given email address (and throws an error if that email address does not exist).

2. Creates a User object to hold that data.

3. Verifies that the user password is correct. If so, it passes back the user object to the caller.

Hooking up Passport and Routes

Now that we have all the plumbing, we need to implement authentication—the back-end user handles, the key passport functions and the user handlers. We can now tie all this into the express app routing directives to ensure everything is secured.

First, we want to be sure that the two routes to create albums and add photos to albums require a logged-in user, as follows:

app.put('/v1/albums.json', alwaysAuthenticated, album_hdlr.create_album);
app.put('/v1/albums/:album_name/photos.json',
        alwaysAuthenticated,
        upload.single("photo_file"),
        album_hdlr.add_photo_to_album);

Here we see the alwaysAuthenticated function we wrote above—we don’t want anybody who isn’t authenticated to be able to access these API endpoints.

Next, we want to be sure that pages with /admin/ in the URL require authentication but that others do not:

app.get('/pages/:page_name', pageAuthenticatedOrNot, page_hdlr.generate);
app.get('/pages/:page_name/:sub_page',
        pageAuthenticatedOrNot,
        page_hdlr.generate);

Finally, we implement the login and logout POST methods that web pages can call (we call these services as a convention to make it clear that they’re doing some processing that’s not CRUD related), as follows:

app.post("/service/login",
         passport.authenticate('local', {
             failureRedirect: '/pages/login?fail',
         }),
         function (req, res) {
             // We want pages to have access to this.
             res.cookie("username", req.user.display_name);
             res.redirect("/pages/admin/home");
         }
        );

app.get('/service/logout', function (req, res) {
    res.cookie("username", "");
    req.logout();
    res.redirect('/');
});

We use passport to authenticate login requests, and to logout, we just call the logout method on the incoming request object. Now, all we have left to do is provide a user interface for these operations.

Creating the Login and Register Pages

For the new user subsystem, you have two new pages in your application: a login page and a registration page. (We first saw this way of doing things in Chapter 6 if you don’t remember it—all of our pages are implemented this way now.) Both are made up of two files, as usual: a JavaScript bootstrapper and an HTML file. For both, the JavaScript bootstrapper is quite standard:

$(function () {
    var tmpl,   // Main template HTML
    tdata = {};  // JSON data object that feeds the template

    // Initialize page
    var initPage = function() {

        // Load the HTML template
        $.get("/templates/login OR register.html", function (d) {
            tmpl = d;
        });

        // When AJAX calls are complete parse the template
        // replacing mustache tags with vars
        $(document).ajaxStop(function () {
            var renderedPage = Mustache.to_html( tmpl, tdata );
            $("body").html( renderedPage );
        });
    }();
});

The HTML for the registration page is shown in Listing 9.2. Apart from showing the HTML for the registration form, it has some JavaScript to ensure that the user has entered all the fields, verify that the two passwords match, and then submit the data to the back-end server. If the login succeeds, you redirect the user back home; otherwise, you show an error and let the user try again.

Listing 9.2 The Registration Page Mustache Template (register.html)


<div style="float: right"><a href="/pages/admin/home">Admin</a></div>
<form name="register" id="register">
  <div id="error" class="error"></div>
  <dl>
    <dt>Email address:</dt>
    <dd><input type="text" size="30" id="email_address" name="email_address"/></dd>
    <dt>Username:</dt>
    <dd><input type="text" size="30" id="display_name" name="display_name"/></dd>
    <dt>Password:</dt>
    <dd><input type="password" size="30" id="password" name="password"/></dd>
    <dt>Password (confirm):</dt>
    <dd><input type="password" size="30" id="password2" name="password2"/></dd>
    <dd><input type="submit" value="Register"/>
  </dl>
</form>

<script type="text/javascript">
$(document).ready(function () {
    if (window.location.href.match(/(fail)/) != null) {
        $("#error").html("Failure creating account.");
    }
});

$("form#register").submit(function (e) {
  if (!$("input#email_address").val()
      || !$("input#display_name").val()
      || !$("input#password").val()
      || !$("input#password2").val()) {
      $("#error").html("You need to enter an email and password.");
  } else if ($("input#password2").val() != $("input#password").val()) {
      $("#error").html("Passwords don't match.");
  } else {
      var info = { email_address: $("input#email_address").val(),
                   display_name: $("input#display_name").val(),
                   password: $("input#password").val() };
      $.ajax({
          type: "PUT",
          url: "/v1/users.json",
          data: JSON.stringify(info),
          contentType: "application/json; charset=utf-8",
          dataType: "json",
          success: function (data) {
              window.location = "/pages/admin/home";
          },
          error: function (data) {
              try {
                  var info = JSON.parse(data.responseText);
                  if (info.error && info.message) {
                      alert(info.error + ": " + info.message);
                  }
              } catch (e) { }
              var ext = window.location.href.match(/(fail)/)
                  ? "" : "?fail";
              window.location = window.location + ext;
              return false;
          }
      });
  }
  return false;
});
</script>


Finally, the code for the login page is shown in Listing 9.3. It is significantly simpler to implement than the register page, as it simply shows a form and submits the results to the server (via POSTing to /service/login). The only additional JavaScript on this page is used to look for the ?fail query parameter to our URL, and if it is present, to show an error message on the page. (This occurs when the user entered invalid credentials.)

Listing 9.3 The Login Page Mustache Template (login.html)


<div style='float: right'><a href='/pages/register'>Register</a></div>
<form name="login" id="login" method="post" action="/service/login">
  <div id="error" class="error"></div>
  <dl>
    <dt>Username:</dt>
    <dd><input type="text" size="30" id="username" name="username"/></dd>
    <dt>Password:</dt>
    <dd><input type="password" size="30" id="password" name="password"/></dd>
    <dd><input type="submit" value="Login"/>
  </dl>
</form>


<script type="text/javascript">

$(document).ready(function () {
    if (window.location.href.match(/(fail)/) != null) {
        $("#error").html("Invalid login credentials.");
    }
});

</script>


With those new files (data/user.js and handlers/users.js, login.js and login.html, and register.js and register.html), you have a complete login system for your web browser front end.

Summary

I threw two new things at you in this chapter: using MySQL databases in your Node applications and adding user authentication to both your web apps and your API servers. For this chapter, I also updated the MongoDB version of the application with the user authentication subsystem in the GitHub tree, so you’re welcome to explore how things are done there as well. By creating two parallel authentication systems, you work to create a good user experience for your browser application, while letting API users still get the full simplicity and power of the JSON API.

In closing out this third part of the book, you learned how to add some powerful new technologies to the photo-sharing application including express and databases and created a fully functioning (if basic) project on which you can build. In the last part of the book, I cover some of the details I have been glossing over thus far in the book. First up, in Chapter 10, you learn how to deploy your applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset