PostgreSQL and NodeJS

Today we’re going to build a CRUD todo single page application with Node, Express, Angular, and PostgreSQL.

node todo app

Technologies/Tools used - Node v0.10.36, Express v4.11.1, Angular v1.3.12.

Project Setup

Start by installing the Express generator if you don’t already have it:

1
$ npm install -g express-generator@4

Then create a new project and install the dependencies:

1
2
$ express node-postgres-todo
$ cd node-postgres-todo && npm install

Add Supervisor to watch for code changes:

1
$ npm install supervisor -g

Update the ‘start’ script in the package.json file:

1
2
3
"scripts": {
  "start": "supervisor ./bin/www"
},

Run the app:

1
$ npm start

Then navigate to http://localhost:3000/ in your browser. You should see the “Welcome to Express” text.

Postgres Setup

Need to setup Postgres? On a Mac? Check out Postgres.app.

With your Postgres server up and listening on port 5432, making a database connection is easy with the pg library:

1
$ npm install pg --save

Now let’s set up a simple table creation script:

1
2
3
4
5
6
7
var pg = require('pg');
var connectionString = process.env.DATABASE_URL || 'postgres://localhost:5432/todo';

var client = new pg.Client(connectionString);
client.connect();
var query = client.query('CREATE TABLE items(id SERIAL PRIMARY KEY, text VARCHAR(40) not null, complete BOOLEAN)');
query.on('end', function() { client.end(); });

Save this as database.js in a new folder called “models”.

Here we create a new instance of Client to interact with the database and then establish communication with it via the connect() method. We then set run a SQL query via the query() method. Communication is closed via the end() method. Be sure to check out the documentation for more info.

Make sure you have a database called “todo” setup, and then run the script to setup the table and subsequent fields:

1
$ node models/database.js

Verify the table/schema creation in psql:

1
2
3
4
5
6
7
8
9
10
11
michaelherman=# \c todo
You are now connected to database "todo" as user "michaelherman".
todo=# \d+ items
                                                     Table "public.items"
  Column  |         Type          |                     Modifiers                      | Storage  | Stats target | Description
----------+-----------------------+----------------------------------------------------+----------+--------------+-------------
 id       | integer               | not null default nextval('items_id_seq'::regclass) | plain    |              |
 text     | character varying(40) | not null                                           | extended |              |
 complete | boolean               |                                                    | plain    |              |
Indexes:
    "items_pkey" PRIMARY KEY, btree (id)

With the database connection setup along with the “items” table, we can now configure the CRUD portion of our app.

Server-Side: Routes

Let’s keep it simple by adding all endpoints to the index.js file within the “routes” folder. Make sure to update the imports:

1
2
3
4
var express = require('express');
var router = express.Router();
var pg = require('pg');
var connectionString = process.env.DATABASE_URL || 'postgres://localhost:5432/todo';

Now, let’s add each endpoint.

Function URL Action
CREATE /api/v1/todos Create a single todo
READ /api/v1/todos Get all todos
UPDATE /api/v1/todos/:todo_id Update a single todo
DELETE /api/v1/todos/:todo_id Delete a single todo

Follow along with the inline comments below for an explanation of what’s happening. Also, be sure to check out the pg documentation to learn about connection pooling. How does that differ from pg.Client?

Create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
router.post('/api/v1/todos', function(req, res) {

    var results = [];

    // Grab data from http request
    var data = {text: req.body.text, complete: false};

    // Get a Postgres client from the connection pool
    pg.connect(connectionString, function(err, client, done) {

        // SQL Query > Insert Data
        client.query("INSERT INTO items(text, complete) values($1, $2)", [data.text, data.complete]);

        // SQL Query > Select Data
        var query = client.query("SELECT * FROM items ORDER BY id ASC");

        // Stream results back one row at a time
        query.on('row', function(row) {
            results.push(row);
        });

        // After all data is returned, close connection and return results
        query.on('end', function() {
            client.end();
            return res.json(results);
        });

        // Handle Errors
        if(err) {
          console.log(err);
        }

    });
});

Test this out via Curl in your terminal:

1
$ curl --data "text=test&complete=false" http://127.0.0.1:3000/api/v1/todos

Then confirm that the data was INSERT’ed correctly into the database via psql:

1
2
3
4
5
todo=# SELECT * FROM items ORDER BY id ASC;
 id | text  | complete
----+-------+----------
  1 | test  | f
(1 row)

Read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
router.get('/api/v1/todos', function(req, res) {

    var results = [];

    // Get a Postgres client from the connection pool
    pg.connect(connectionString, function(err, client, done) {

        // SQL Query > Select Data
        var query = client.query("SELECT * FROM items ORDER BY id ASC;");

        // Stream results back one row at a time
        query.on('row', function(row) {
            results.push(row);
        });

        // After all data is returned, close connection and return results
        query.on('end', function() {
            client.end();
            return res.json(results);
        });

        // Handle Errors
        if(err) {
          console.log(err);
        }

    });

});

Add a few more rows of data via Curl, and then test the endpoint out in your browser at http://localhost:3000/api/v1/todos. You should see an array of JSON objects:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[
    {
        id: 1,
        text: "test",
        complete: false
    },
    {
        id: 2,
        text: "test2",
        complete: false
    },
    {
        id: 3,
        text: "test3",
        complete: false
    }
]

Update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
router.put('/api/v1/todos/:todo_id', function(req, res) {

    var results = [];

    // Grab data from the URL parameters
    var id = req.params.todo_id;

    // Grab data from http request
    var data = {text: req.body.text, complete: req.body.complete};

    // Get a Postgres client from the connection pool
    pg.connect(connectionString, function(err, client, done) {

        // SQL Query > Update Data
        client.query("UPDATE items SET text=($1), complete=($2) WHERE id=($3)", [data.text, data.complete, id]);

        // SQL Query > Select Data
        var query = client.query("SELECT * FROM items ORDER BY id ASC");

        // Stream results back one row at a time
        query.on('row', function(row) {
            results.push(row);
        });

        // After all data is returned, close connection and return results
        query.on('end', function() {
            client.end();
            return res.json(results);
        });

        // Handle Errors
        if(err) {
          console.log(err);
        }

    });

});

Again, test via Curl:

1
$ curl -X PUT --data "text=test&complete=true" http://127.0.0.1:3000/api/v1/todos/1

Navigate to http://localhost:3000/api/v1/todos to make sure the data has been updated correctly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[
    {
        id: 1,
        text: "test",
        complete: true
    },
    {
        id: 2,
        text: "test2",
        complete: false
    },
    {
        id: 3,
        text: "test3",
        complete: false
    }
]

Delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
router.delete('/api/v1/todos/:todo_id', function(req, res) {

    var results = [];

    // Grab data from the URL parameters
    var id = req.params.todo_id;


    // Get a Postgres client from the connection pool
    pg.connect(connectionString, function(err, client, done) {

        // SQL Query > Delete Data
        client.query("DELETE FROM items WHERE id=($1)", [id]);

        // SQL Query > Select Data
        var query = client.query("SELECT * FROM items ORDER BY id ASC");

        // Stream results back one row at a time
        query.on('row', function(row) {
            results.push(row);
        });

        // After all data is returned, close connection and return results
        query.on('end', function() {
            client.end();
            return res.json(results);
        });

        // Handle Errors
        if(err) {
          console.log(err);
        }

    });

});

Final Curl test:

1
$ curl -X DELETE http://127.0.0.1:3000/api/v1/todos/3

And you should now have:

1
2
3
4
5
6
7
8
9
10
11
12
[
    {
        id: 1,
        text: "test",
        complete: true
    },
    {
        id: 2,
        text: "test2",
        complete: false
    }
]

Refactoring

Before we jump to the client-side to add Angular, be aware that our code should be refactored to address a few issues. We’ll handle this later on in this tutorial, but this is an excellent opportunity to refactor the code on your own. Good luck!

Client-Side: Angular

Let’s dive right in to Angular.

Keep in mind that this is not meant to be an exhaustive tutorial. If you’re new to Angular I suggest following my “AngularJS by Example” tutorial - Building a Bitcoin Investment Calculator.

Module

Create a file called app.js in the “public/javascripts” folder. This file will house our Angular module and controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
angular.module('nodeTodo', [])

.controller('mainController', function($scope, $http) {

    $scope.formData = {};
    $scope.todoData = {};

    // Get all todos
    $http.get('/api/v1/todos')
        .success(function(data) {
            $scope.todoData = data;
            console.log(data);
        })
        .error(function(error) {
            console.log('Error: ' + error);
        });
});

Here we define our module as well as the controller. Within the controller we are using the $http service to make an AJAX request to the '/api/v1/todos' endpoint and then updating the scope accordingly.

What else is going on?

Well, we’re injecting the $scope and $http services. Also, we’re defining and updating $scope to handle binding.

Update / Route

Let’s update the main route in index.js within the “routes” folder:

1
2
3
router.get('/', function(req, res, next) {
  res.sendFile(path.join(__dirname, '../views', 'index.html'));
});

So when the end user hits the main endpoint, we send the index.html file. This file will contain our HTML and Angular templates.

Make sure to add the following dependency as well:

1
var path = require('path');

View

Now, let’s add our basic Angular view within index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html ng-app="nodeTodo">
  <head>
    <title>Todo App - with Node + Express + Angular + PostgreSQL</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="http://netdna.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" media="screen">
  </head>
  <body ng-controller="mainController">
    <div class="container">
      <ul ng-repeat="todo in todoData">
        <li></li>
      </ul>
    </div>
    <script src="http://code.jquery.com/jquery-1.11.2.min.js" type="text/javascript"></script>
    <script src="http://netdna.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js" type="text/javascript"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.12/angular.min.js"></script>
    <script src="javascripts/app.js"></script>
  </body>
</html>

This should all be straightforward. We bootstrap Angular - ng-app="nodeTodo", define the scope of the controller - ng-controller="mainController" - and then use ng-repeat to loop through the todoData object, adding each individual todo to the page.

Module (round two)

Next, let’s update the module to handle the Create and Delete functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Create a new todo
$http.post('/api/v1/todos', $scope.formData)
    .success(function(data) {
        $scope.formData = {};
        $scope.todoData = data;
        console.log(data);
    })
    .error(function(error) {
        console.log('Error: ' + error);
    });

// Delete a todo
$http.delete('/api/v1/todos/' + todoID)
    .success(function(data) {
        $scope.todoData = data;
        console.log(data);
    })
    .error(function(data) {
        console.log('Error: ' + data);
    });

Now, let’s update our view…

View (round two)

Simply update each list item like so:

1
<li><input type="checkbox" ng-click="deleteTodo(todo.id)">&nbsp;</li>

This uses the ng-click directive to call the deleteTodo() function - which we still need to define - that takes a unique id associated with each todo as an argument.

Module (round three)

Update the controller:

1
2
3
4
5
6
7
8
9
10
11
// Delete a todo
$scope.deleteTodo = function(todoID) {
    $http.delete('/api/v1/todos/' + todoID)
        .success(function(data) {
            $scope.todoData = data;
            console.log(data);
        })
        .error(function(data) {
            console.log('Error: ' + data);
        });
};

We simply wrapped the delete functionality in the deleteTodo() function. Test this out. Make sure that when you click a check box the todo is removed.

View (round three)

To handle the creation of a new todo, we need to add an HTML form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="container">

  <form>
    <div class="form-group">
      <input type="text" class="form-control input-lg" placeholder="Add a todo..." ng-model="formData.text">
    </div>
    <button type="submit" class="btn btn-primary btn-lg" ng-click="createTodo()">Add Todo</button>
  </form>

  <ul ng-repeat="todo in todoData">
    <li><input type="checkbox" ng-click="deleteTodo(todo.id)">&nbsp;</li>
  </ul>

</div>

Again, we use ng-click to call a function in the controller.

Module (round four)

1
2
3
4
5
6
7
8
9
10
11
12
// Create a new todo
$scope.createTodo = function(todoID) {
    $http.post('/api/v1/todos', $scope.formData)
        .success(function(data) {
            $scope.formData = {};
            $scope.todoData = data;
            console.log(data);
        })
        .error(function(error) {
            console.log('Error: ' + error);
        });
};

Test this out!

View (round four)

With the main functionality done, let’s update the front-end to make it look, well, presentable.

HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html ng-app="nodeTodo">
  <head>
    <title>Todo App - with Node + Express + Angular + PostgreSQL</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- styles -->
    <link href="http://netdna.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" media="screen">
    <link href="stylesheets/style.css" rel="stylesheet" media="screen">
  </head>
  <body ng-controller="mainController">

    <div class="container">

      <div class="header">
        <h1>Todo App</h1>
        <hr>
        <h1 class="lead">Node + Express + Angular + PostgreSQL</h1>
      </div>

      <div class="todo-form">
        <form>
          <div class="form-group">
            <input type="text" class="form-control input-lg" placeholder="Enter text..." ng-model="formData.text">
          </div>
          <button type="submit" class="btn btn-primary btn-lg btn-block" ng-click="createTodo()">Add Todo</button>
        </form>
      </div>

      <br>

      <div class="todo-list">
        <ul ng-repeat="todo in todoData">
          <li><h3><input class="lead" type="checkbox" ng-click="deleteTodo(todo.id)">&nbsp;</li></h3><hr>
        </ul>
      </div>

    </div>

    <!-- scripts -->
    <script src="http://code.jquery.com/jquery-1.11.2.min.js" type="text/javascript"></script>
    <script src="http://netdna.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js" type="text/javascript"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.12/angular.min.js"></script>
    <script src="javascripts/app.js"></script>
  </body>
</html>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
body {
  padding: 50px;
  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}

ul {
  list-style-type: none;
  padding-left: 10px;
}

.container {
  max-width: 400px;
  background-color: #eeeeee;
  border: 1px solid black;
}

.header {
  text-align: center;
}

How’s that? Not up to par? Continue working on it on your end.

Refactoring (for real)

Now that we added the front-end functionality, let’s update our application’s structure and refactor parts of the code.

Structure

Since our application is logically split between the client and server, let’s do the same for our project structure. So, make the following changes to your folder structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
├── app.js
├── bin
│   └── www
├── client
│   ├── public
│   │   ├── javascripts
│   │   │   └── app.js
│   │   └── stylesheets
│   │       └── style.css
│   └── views
│       └── index.html
├── config.js
├── package.json
└── server
    ├── models
    │   └── database.js
    └── routes
        └── index.js

Now, we need to make a few updates to the code:

server/routes/index.js:

1
res.sendFile(path.join(__dirname, '../', '../', 'client', 'views', 'index.html'));

app.js:

1
var routes = require('./server/routes/index');

app.js:

1
app.use(express.static(path.join(__dirname, './client', 'public')));

Configuration

Next, let’s move the connectionString variable - which specifies the database URI (process.env.DATABASE_URL || 'postgres://localhost:5432/todo';) - to a configuration file since we are reusing the same same connection throughout our application.

Create a file called config.js in the root directory, and then add the following code to it:

1
2
3
var connectionString = process.env.DATABASE_URL || 'postgres://localhost:5432/todo';

module.exports = connectionString;

Then update the connectionString variable in both server/models/database.js and server/routes/index.js:

1
var connectionString = require(path.join(__dirname, '../', '../', 'config'));

And make sure to add var path = require('path'); to the former file as well.

Utility Function

Did you notice in our routes that we are reusing the same code in each of the CRUD functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SQL Query > Select Data
var query = client.query("SELECT * FROM items ORDER BY id ASC");

// Stream results back one row at a time
query.on('row', function(row) {
    results.push(row);
});

// After all data is returned, close connection and return results
query.on('end', function() {
    client.end();
    return res.json(results);
});

// Handle Errors
if(err) {
  console.log(err);
}

We should abstract that out into a utility function so we’re not duplicating code. Do this on your own, and then post a link to your code in the comments for review.

Conclusion and next steps

That’s it! Now, since there’s a number of moving pieces here, please review how each piece fits into the overall process and whether each is part of the client or server-side. Comment below with questions. Grab the code from the repo.



Finally, this app is far from finished. What else do we need to do?

  1. Handle Permissions via passport.js
  2. Add a task runner - like Gulp
  3. Test with Mocha and Chai
  4. Check test coverage with Istanbul
  5. Add promises
  6. Use Bower for managing client-side dependencies
  7. Utilize Angular Routing, form validation, Services, and Templates
  8. Handle updates/PUT requests
  9. Update the Express View Engine to HTML
  10. Better manage the database layer by adding an ORM - like Sequalize - and a means of managing migrations

What else? Comment below.

Sublime Text for Web Developers

Sublime Text 3 (ST3) is a powerful editor just as it is. But if you want to step up your game, you need to take advantage of all that ST3 has to offer by learning the keyboard shortcuts and customizing the editor to meet your individual needs…

NOTE: This tutorial is meant for Mac OS X users, utilizing HTML, CSS, and JavaScript/jQuery.

Be sure to set up the subl command line tool, which can be used to open a single file or an entire project directory of files and folders, before moving on.

Keyboard Shortcuts

Goal: Never take your hands off the keyboard!

  1. Command Palette (CMD-SHIFT-P) - Accesses the all-powerful Command Palette, where you can run toolbar actions - setting the code syntax, accessing package control, renaming a file, etc..

    Command Palette

  2. Goto Anything (CMD-P) - Searches for a file within the current project or a line or definition in the current file. It’s fuzzy so you don’t need to match the name exactly.

    • @ - Definition - class, method, function
    • : - Line #
  3. Distraction Free Mode (CMD-CTRL-SHIFT-F) - Eliminates distractions!

    Command Palette

  4. Hide/Show the Sidebar (CMD-K, CMD-B) - Toggles the sidebar.

  5. Comment Your Code (CMD-/) - Highlight the code you want to comment out, then comment it out. If you do not highlight anything, this command will comment out the current line.
  6. Highlight an entire line (CMD-L)
  7. Delete an entire line (CMD-SHIFT-K)
  8. Multi-Edit (CMD+D) - Simply select the word you want to edit, and press CMD-D repeatedly until you have selected all the words you want to change/update/etc..

Grab the cheat sheet in PDF.

Configuration

You can customize almost anything in ST3 by updating the config settings.

Config settings can be set at the global/default-level or by user, project, package, and/or syntax. Setting files are loaded in the following order:

  • Packages/Default/Preferences.sublime-settings
  • Packages/User/Preferences.sublime-settings
  • Packages/<syntax>/<syntax>.sublime-settings
  • Packages/User/<syntax>.sublime-settings

Always apply your custom configuration settings to at the User level, since they will not get overridden when you update Sublime and/or a specific package.

  1. Base User Settings: Sublime Text 3 > Preferences > Settings – User
  2. Package User Specific: Sublime Text 3 > Preferences > Package Settings > PACKAGE NAME > Settings – User
  3. Syntax User Settings: Sublime Text 3 > Preferences > Settings – More > Syntax Specific - User

Base User Settings

Don’t know where to start?

1
2
3
4
5
6
7
8
{
  "draw_white_space": "all",
  "rulers": [80],
  "tab_size": 2,
  "translate_tabs_to_spaces": true,
  "trim_trailing_white_space_on_save": true,
  "word_wrap": true
}

Add this to Sublime Text 3 > Preferences > Settings – User.

What’s happening?

  1. We convert tabs to two spaces. Now when you press tab, it actually indents two spaces. This is perfect for HTML, CSS, and JavaScript. This creates cleaner, easier to read code.
  2. The ruler is a simple reminder to keep your code concise (for readability).
  3. We added white space markers and trimmed any trailing (err, unnecessary) white space on save.
  4. Finally, word wrapping is automatically applied

What else can you update? Start with the theme.

For example -

1
"color_scheme": "Packages/User/Flatland Dark (SL).tmTheme",

Simply add this to that same file.

You can find and test themes online before applying them here.

Advanced users should look into customizing key bindings, macros, and code snippets.

Packages

Want more features? There’s a ton of extensions used to, well, extend ST3’s functionality written by the community. “There’s a package for that”.

Package Control

Package Control must be installed manually, then, once installed, you can use it to install other ST3 packages. To install, copy the Python code for found here. Then open your console (CTRL-`), paste the code, press ENTER. Then Reboot ST3.

Command Palette

Now you can easily install packages by entering the Command Palette (remember the keyboard shortcut?).

  1. Type “install”. Press ENTER when Package Control: Install Package is highlighted
  2. Search for a package. Boom!

Let’s look at some packages…

Sublime Linter

SublimeLinter is a framework for Sublime Text linters.

After you install the base package, you need to install linters separately via Package Control, which are easily searchable as they adhere to the following naming syntax - SublimeLinter-[linter_name]. You can view all the official linters here.

Start with the following linters:

  1. SublimeLinter-jshint
  2. SublimeLinter-csslint
  3. SublimeLinter-html-tidy
  4. SublimeLinter-json

Sidebar Enhancements

Sidebar Enhancements extends the number of menu options in the sidebar, adding file explorer actions - i.e., Copy, Cut, Paste, Delete, Rename. This package also adds the same commands/actions to the Command Palette.

Command Palette

JsFormat

JsFormat beautifies your JavaScript/jQuery Code!

Press CTRL-ALT-F to turn this mess…

1
2
function peopleFromBoulder(arr) {return arr.filter(function(val) {return val.city == 'Boulder';})
    .map(function(val) {return val.name + ' is from Boulder';});}

…into…

1
2
3
4
5
6
7
8
function peopleFromBoulder(arr) {
    return arr.filter(function(val) {
            return val.city == 'Boulder';
        })
        .map(function(val) {
            return val.name + ' is from Boulder';
        });
}

DocBlockr

DocBlockr creates comment blocks based on the context.

Try it!

1
2
3
4
5
6
7
8
function refactorU (student) {
    if (student === "Zach") {
        var str = student + " is awesome!";
    } else {
        var str = student + " is NOT awesome!";
    }
    return str;
}

Now add an opening comment block - /** - and as soon as you press tab, it will create a dummy-documentation-comment automatically.

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * [refactorU description]
 * @param  {[type]}
 * @return {[type]}
 */
function refactorU (student) {
    if (student === "Zach") {
        return student + " is awesome!";
    } else {
        return student + " is NOT awesome!";
    }
}

Yay!

GitGutter

GitGutter displays icons in the “gutter” area (next to the line numbers) indicating whether an individual line has been modified since your last commit.

GitGutter

Emmet

With Emmet you can turn a symbol or code abbreviation into a HTML or CSS code snippet. It’s by far the best plugin for increasing your productivity and efficiency as a web developer.

Try this out: Once installed, start a new HTML file, type a bang, !, and then press tab.

1
2
3
4
5
6
7
8
9
10
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>

</body>
</html>

Boom!

Check the official docs to see all the expressions/symbols/abbreviations that can be used for generating snippets.

Conclusion

Go pimp your editor.

Want a package? It’s just Python. Hire me!

Comment below. Check out the repo for my Sublime dotfiles. Cheers!

Additional Resources

  1. Sublime Text Tips Newsletter - awesome tips, tricks
  2. Community-maintained documentation
  3. Package Manager documentation
  4. Unofficial documentation reference
  5. Setting Up Sublime Text 3 for Full Stack Python Development - my other ST3 post

User Authentication With Passport and Express 4

This post demonstrate how to add user authentication to Node/Express with Passport.js.

If you’re interested in social authentication via Passport, please check out this blog post. Looking for an Express 3 authentication tutorial? Check out this post.

Before you start, make sure you have Node installed for your specific operating system. This tutorial also uses the following tools/technologies:

Contents

  1. Project Setup
  2. Edit app.js
  3. Mongoose
  4. Add Routes
  5. Test
  6. Views
  7. Test Redux
  8. Unit Tests
  9. Error Handling
  10. Conclusion

Project Setup

Start by installing the Express generator, which we’ll use to generate a basic project boilerplate:

1
$ npm install -g express-generator@4

The -g flag means that we’re installing this globally, on our entire system.

Navigate to a convenient directory, like your “Desktop” or “Documents”, then create your app:

1
$ express passport-local-express4

Check out the project structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.jade
    ├── index.jade
    └── layout.jade

This took care of the heavy lifting, adding common files and functions associated with all apps.

Install/Update Dependencies

Update the package.json file to reference the correct dependencies:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "name": "passport-local-express4",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "repository": {
    "type": "git",
    "url": "git@github.com:mjhea0/passport-local-express4.git"
  },
  "author": "Michael Herman <michael@mherman.org>",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.10.2",
    "chai": "~1.8.1",
    "cookie-parser": "^1.3.3",
    "express": "^4.11.1",
    "express-session": "^1.10.1",
    "jade": "^1.9.1",
    "mocha": "~1.14.0",
    "mongoose": "^3.8.22",
    "morgan": "^1.5.1",
    "passport": "^0.2.1",
    "passport-local": "^1.0.0",
    "passport-local-mongoose": "^1.0.0",
    "should": "~2.1.0",
    "serve-favicon": "^2.2.0",
    "debug": "^2.1.1"
  }
}

Now install the dependencies:

1
2
$ cd express-local-express4
$ npm install

Sanity Check

Let’s test our setup by running the app:

1
$ node ./bin/www

Navigate to http://localhost:3000/ in your browser and you should see the “Welcome to Express” text staring back.

Setup MongoDB

Install:

1
$ npm install -g mongodb

Then, in a new terminal window, start the MongoDB daemon:

1
$ sudo mongod

Edit app.js

Update the Requirements

Add the following requirements:

1
2
3
var mongoose = require('mongoose');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;

Update app.js

Update all of app.js with the following code (check the comments for a brief explanation):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// dependencies
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

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

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(require('express-session')({
    secret: 'keyboard cat',
    resave: false,
    saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(express.static(path.join(__dirname, 'public')));


app.use('/', routes);

// passport config
var Account = require('./models/account');
passport.use(new LocalStrategy(Account.authenticate()));
passport.serializeUser(Account.serializeUser());
passport.deserializeUser(Account.deserializeUser());

// mongoose
mongoose.connect('mongodb://localhost/passport_local_mongoose_express4');

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});


module.exports = app;

Mongoose

Let’s get the Mongoose up and running. Add a new file called account.js to a new directory called “models” with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var passportLocalMongoose = require('passport-local-mongoose');

var Account = new Schema({
    username: String,
    password: String
});

Account.plugin(passportLocalMongoose);

module.exports = mongoose.model('Account', accounts);

You may be wondering about password security, specifically salting/hashing the password. Fortunately, the passport-local-mongoose package automatically takes care of salting and hashing the password for us. More on this further down.

Sanity Check

Again, test the app:

1
$ node ./bin/www

Make sure you stil see the same “Welcome to Express” text.

Add Routes

Within the “routes” folder, add the following code to the index.js file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var express = require('express');
var passport = require('passport');
var Account = require('../models/account');
var router = express.Router();


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

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

router.post('/register', function(req, res) {
    Account.register(new Account({ username : req.body.username }), req.body.password, function(err, account) {
        if (err) {
            return res.render('register', { account : account });
        }

        passport.authenticate('local')(req, res, function () {
            res.redirect('/');
        });
    });
});

router.get('/login', function(req, res) {
    res.render('login', { user : req.user });
});

router.post('/login', passport.authenticate('local'), function(req, res) {
    res.redirect('/');
});

router.get('/logout', function(req, res) {
    req.logout();
    res.redirect('/');
});

router.get('/ping', function(req, res){
    res.status(status).send("pong!", 200);
});

module.exports = router;

Test

Fire up the server. Navigate to http://localhost:3000/ping. Make sure you do not get any errors and that you see the word “pong!”.

Views

layout.jade

Update:

1
2
3
4
5
6
7
8
9
10
11
12
doctype html
html
  head
    title= title
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    link(href='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css', rel='stylesheet', media='screen')
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    block content

  script(src='http://code.jquery.com/jquery.js')
  script(src='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js')

index.jade

Update:

1
2
3
4
5
6
7
8
9
10
extends layout

block content
  if (!user)
    a(href="/login") Login
    br
    a(href="/register") Register
  if (user)
    p You are currently logged in as #{user.username}
    a(href="/logout") Logout

login.jade

Add a new file called login.jade to the views:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extends layout

block content
  .container
    h1 Login Page
    p.lead Say something worthwhile here.
    br
    form(role='form', action="/login",method="post", style='max-width: 300px;')
      .form-group
          input.form-control(type='text', name="username", placeholder='Enter Username')
      .form-group
        input.form-control(type='password', name="password", placeholder='Password')
      button.btn.btn-default(type='submit') Submit
      &nbsp;
      a(href='/')
        button.btn.btn-primary(type="button") Cancel

register.jade

Add another file called register.jade to the views:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extends layout

block content
  .container
    h1 Register Page
    p.lead Say something worthwhile here.
    br
    form(role='form', action="/register",method="post", style='max-width: 300px;')
      .form-group
          input.form-control(type='text', name="username", placeholder='Enter Username')
      .form-group
        input.form-control(type='password', name="password", placeholder='Password')
      button.btn.btn-default(type='submit') Submit
      &nbsp;
      a(href='/')
        button.btn.btn-primary(type="button") Cancel

Test redux

Fire up the server and test! Register, and then login.

Remember how I said that we’d look at salting and hashing a password again? Well, let’s check our Mongo database to ensure that it’s working.

When I tested the user registration, I used “michael” for both my username and password.

Let’s see what this looks like in the database:

1
2
3
4
5
6
7
8
$ mongo
MongoDB shell version: 2.4.6
connecting to: test
> use passport_local_mongoose_express4
switched to db passport_local_mongoose_express4
> db.accounts.find()
{ "salt" : "9ffd63f2bcce58bf79691cacfaae678f690dd73ef778445bf79f97c41934189b", "hash" : "17eabe62d459acdb4f3d8eaab7369a1e989c6150e231d1e87a7cf1c31dfc7eafc0616732a6db8f08c413dcbec06c95d512cef55503a1fe9a7ed5dc15ecf5cf67c114af5a659c79bb47039082a3af933e1c32dd2519b8be11596a775e1d262fd53437927e0fd948b76e738f342904a598e6c533445351c9b3d629aa118adfbe0646a80539e816c06248e353b1787dbd8c646a2ed018bbf5e58fb6a6cc1f32c6ea61b3e52230cfdf75a9f4b7ba20b3d3ae3b86f5816f5df9c48f9d1bb4a9c42e30bf646c3810d050847c1905e5a95f53c81078090e42ba58799187a61b047376def48fb640a4f48eca4c7f35610eafc2c770e61172b11c7e98c36281983de56414fa95e0708c9a6458a903baaf3818a3e4675b39418b358f51f45aca792e606f692e0a7d3667d111d00d0f521257d3486cbcff250dc7d9859ab80f9d56a3d272fb0ebb2e7dd969c0749361153c6bde62ad50b3d47233424034b959c78225db000cc1416aa0d555016f1b666d2da709e69c5030ee39753597a1d06ec0a4e001e22bff37947c1b993794d21667dc6c65e4116dd5ca216a161aa9026063e0b12e1165ffa5c827a6803df6765766cc55bcca122cd4d9f572353a988f90200ffc4a610d9eca83df01d6f30af78f9ec476fc974bc1d3a5fd2759a56486795bd7d993462a8d2f9b9c42d3197cd7b9855f17eaac4073a4d843d56b5c9a75b86cc1bb8b27ec", "username" : "michael", "_id" : ObjectId("54c7bbbfaf54064909921a36"), "__v" : 0 }
>

So, you can see that we have a document with five keys:

  • username is as we expected - “michael”
  • _id pertains to the unique id associated with that document.
  • __v is the version # for that specific documents.
  • Finally, instead of a password key we have both a salt and a hash key. For more on how these are generated, please refer to the passport-local-mongoose documentation.

Unit tests

First, update the scripts object in package.json:

1
2
3
4
"scripts": {
  "start": "node ./bin/www",
  "test": "make test"
 },

Now add a Makefile to the root and include the following code:

1
2
3
4
test:
    @./node_modules/.bin/mocha

.PHONY: test

Take note of the spacing on the second line. This must be a tab or you will see an error.

Run make test from the command line. If all is well, you should see - 0 passing (1ms). Now we just need to add some tests…

Add tests

Create a new folder called “tests”, and then adde a new file called *test.user.js”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
var should = require("should");
var mongoose = require('mongoose');
var Account = require("../models/account.js");
var db;

describe('Account', function() {

    before(function(done) {
        db = mongoose.connect('mongodb://localhost/test');
            done();
    });

    after(function(done) {
        mongoose.connection.close();
        done();
    });

    beforeEach(function(done) {
        var account = new Account({
            username: '12345',
            password: 'testy'
        });

        account.save(function(error) {
            if (error) console.log('error' + error.message);
            else console.log('no error');
            done();
        });
    });

    it('find a user by username', function(done) {
        Account.findOne({ username: '12345' }, function(err, account) {
            account.username.should.eql('12345');
            console.log("   username: ", account.username);
            done();
        });
    });

    afterEach(function(done) {
        Account.remove({}, function() {
            done();
        });
     });

});

No run make tests. You should see that it passed - 1 passing (43ms).

Error handling

Right now we have some poorly handled errors that are confusing to the end user. For example, try to register a name that already exists, or login with a username that doesn’t exist. This can and should be handled better.

Registration

First, update the /register route so an error is thrown, which gets sent to Jade template, if a user tries to register a username that already exists:

1
2
3
4
5
6
7
8
9
10
11
router.post('/register', function(req, res) {
    Account.register(new Account({ username : req.body.username }), req.body.password, function(err, account) {
        if (err) {
          return res.render("register", {info: "Sorry. That username already exists. Try again."});
        }

        passport.authenticate('local')(req, res, function () {
            res.redirect('/');
        });
    });
});

Then add the following code to the bottom of the “register.jade” template:

1
2
br
h4= info

Test this out.

Next, if you try to login with a username and password combo that does not exist, the user is redirected to a page with just the word “Unauthorized” on it. This is confusing and unhelpful. See if you can fix this on your own. Cheers!

Conclusion

That’s it. Grab the code from the repository. Cheers!