Stellar Chariot

By Arun Neelakandan

Express and Node.js for Djangonauts: A Tutorial on Building a Polls App, Part 1

I thought I’d learn about Node.js and the Express web framework, so I decided to implement the polls app that the Django tutorial discusses — since I’m quite familiar with Django.

This tutorial will be split into parts.

In this part of the tutorial, I’ll be covering what part 1 of the official Django tutorial covers. That is, setting up our project environment and familiarising ourselves with Express and also to touch on basic database queries.

Before we proceed, some things to begin to keep in mind:

  • Express is a minimalistic framework compared to Django
  • Unlike Django, Express doesn’t enforce a particular structure or convention to your code (but some example arrangements are provided)
  • Node.js is asynchronous (I’m still trying to wrap my head around this)
  • I’m a noob to Node.js and Express. So there might be better ways to do what I’m doing (feel free to tell me in such case!)

Prerequisites

You should:

  • Know a bit about JavaScript
  • Have a working knowledge of HTTP
  • Be comfortable with using the command-line/terminal
  • Know how to install things in your OS (e.g. using Homebrew, apt-get or regular installers)
  • Have a vague understanding what Node.js is and how Express (and Connect) fit in

Note that this tutorial is targeted to those who’ve used Django before, but you might find the tutorial useful even if you don’t know Django.

Context

  • I wrote this tutorial on a Mac running OS X 10.8 (but you should be able adapt to your OS)
  • I have the awesome Homebrew installed
  • Written circa 7 July 2013 (see the package.json file for version numbers)
  • Using Node.js version 0.10.12
  • At the time of writing, the latest stable version of Django is 1.5.1

Code on Github

You can find the code used in this tutorial on Github under the ‘part-1’ tag:

View Code for Part 1 on Github

Instructions

Install Node.js (and npm)

Install Node.js. I installed it using the installer on Node.js website.

Check that it’s installed properly by running node –version in the command-line. You should see a version number outputted (if not, Node hasn’t been installed properly.)

Also check whether npm (Node Package Manager) — which is installed when you install Node and is like the ‘pip’ in the Python world — has been installed properly: npm –version

Install Express Globally

Install the Express executable and package globally: npm install -g express

Creating a Project

Now let’s create a template project called ‘polls’: express polls

This is similar to running django-admin.py startproject polls in Django and creates a set of files and folders for you:

  • app.js: the main app file and starting point of the application (somewhat similar to settings.py and also urls.py in Django)
  • package.json: dependencies and such (similar to requirements.txt that contain Python dependencies, generated by pip freeze)
  • public: static files go here (somewhat like the STATIC_ROOT folder)
  • routes: routes (like a blend of Django’s urls.py and views.py)
  • views: similar to the templates in Django

Installing Project Dependencies

Go into the ‘polls’ folder and install dependencies: cd polls && npm install

npm install installs the dependencies listed in the package.json file. Effectively, this command is akin to running pip install -r requirements.txt in Django/PIP.

You might be wondering why Express is listed as a project dependency in package.json as well as being installed globally. Here’s why.

Running the App (The Server)

Run node app and open http://localhost:3000/ (or whatever URL is shown in your terminal) in your browser and you should see a ‘Welcome to Express’ message:

'Welcome to Express' webpage

Database Setup: Enter MongoDB

We’ll be using MongoDB since we’re trying out novel things. Why MongoDB and not something else? It looks like MongoDB and JavaScript/Node.js make a good pairing — especially since MongoDB stores JSON-style documents.

Install MongoDB using your favourite method. I installed it using Homebrew: brew install mongodb. You might like to just grab an installer from the MongoDB downloads page.

If you don’t know much about MongoDB (just like me!), I’d recommend doing the MongoDB intro tutorial on the online shell and checking out the getting started with MongoDB docs.

Installing Dependencies: Mongoose

Unlike Django, Express itself doesn’t come with an ORM. This is left up to the developer. Because we’re using MongoDB, I chose mongoose as the ORM.

To install a dependency, you can just run npm install mongoose. But don’t do that.

Instead, run npm info mongoose version to grab the current version number of mongoose, add this dependency to the package.json file and install: npm install

Why do it this way? Then we can keep track of dependencies in a file (just like we do with requirements.txt in typical Django projects).

Create Database in MongoDB

Run the MongoDB server (if it’s not already running): mongod

In another terminal window or tab, start the mongo tool: mongo

Once inside the tool, type use pollsdb to create the database. Type exit or Control-D to exit the tool. The session might look like something like this:

1
2
3
4
5
6
$ mongo
MongoDB shell version: 2.4.4
connecting to: test
> use pollsdb
> exit
bye

Creating Models

Create a new directory inside the project directory: mkdir models

Create a file ‘db.js’ inside this folder: touch models/db.js

Copy-paste this into the file and save:

db.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
var mongoose = require('mongoose')

var pollSchema = new mongoose.Schema({
  question: {type: String, required: true},
  pubDate: {type: Date, required: true},
  // Can have subdocuments 
  // (data not normalised into separate table/collection)
  choices: [{
    choiceText: String,
    votes: {
      type: Number,
      required: true,
      default: 0
    }
  }]
}, {
  // Set the MongoDB collection name
  // See note on: http://mongoosejs.com/docs/api.html#index_Mongoose-model
  // See option 'collection' on: http://mongoosejs.com/docs/api.html#index_Mongoose-Schema
  collection: 'poll'
})

// We can easily add methods to models
pollSchema.methods.wasPublishedRecently = function () {
  var now = new Date()
  var delta = Math.abs(now.getTime() - this.pubDate.getTime())
  // If the difference is less than
  // the number of milliseconds in a day
  return delta <= (60 * 60 * 24 * 1000)
}

exports.pollSchema = pollSchema

// Register the model
// Optional third argument specifies collection
// See: http://stackoverflow.com/a/7997403/977931
// And see note on: http://mongoosejs.com/docs/api.html#index_Mongoose-model
mongoose.model('Poll', pollSchema)

Playing with Mongoose API

You can interact with the database via the Mongoose API via the command-line as this interactive node shell session demonstrates. Just fire up the Node.js REPL with: node. This is similar to starting up the Python interpreter with python (or python manage.py shell).

Note though: Because of asynchrous nature of Node.js (a lot of callbacks), I’d recommend using the mongo tool for queries and then incorporating that into mongoose later. Using mongoose inside the node REPL is tedious, as there seem to be a lot of nested callbacks which make it hard to 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
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
$ node
> var mongoose = require('mongoose')
> var pollsdb = require('./models/db')
> mongoose.connect('mongodb://localhost/pollsdb')
> var conn = mongoose.connection
> conn.on('error', console.error.bind(console, 'Database connection error:'));
> conn.once('open', function callback () { console.log("Hey handsome. Database connected.") })
> 
> var Poll = conn.model("Poll", pollsdb.pollSchema)
> Poll.find(function(err, polls) { if (err) console.log(err); else console.log(polls); }); 
> 
> // Create a question
> var p = {question: "Which do you usually eat?", pubDate: new Date('Dec 03, 2013'), choices: [{choiceText: 'Rice', votes: 7}, {choiceText: 'Chocolate', votes: 9}, {choiceText: 'Fish', votes: 4}]};
> var food = new Poll(p)
> 
> // My mother always said not to waste food
> food.save(function (err, food) { if (err) console.log(err); else console.log(food); });
> food.question
> 
> // See: http://stackoverflow.com/q/15724272/977932
> food.id
> // TODO: Figure out a toString()/__unicode__() equivalent
> food
> 
> // Let us do a few queries
> // `Poll.objects.all()` equivalent
> Poll.find(function(err, polls) { if (err) console.log(err); else console.log(polls); }); 
> 
> // `Poll.objects.filter(whatever)` equivalent
> Poll.find({question: 'Which do you usually eat?'}, function(err, polls) {
if (err) 
  console.log(err); 
else 
  console.log(polls); 
}); 
> 
> // `Poll.objects.filter(question__startswith='What')` equivalent
> Poll.find({question: /^Which do you/}, function(err, polls) { 
if (err) 
  console.log(err); 
else 
  console.log(polls); 
});
> 
> // Get the poll that was published this year
> // Seems like this is the best/simplest way to do this (see: http://stackoverflow.com/q/17508111/977931) 
> Poll.find({pubDate: { $gte: new Date("1 Jan, 1999"), $lt: new Date("1 Jan, 2000")}}, function(err, polls) {console.log(polls);} );
> 
> // Get one poll (note how no exceptions are thrown when no poll is found)
> Poll.findOne({question: /^Non existent question/}, function(err, polls) { 
if (err) 
  console.log("Uh-oh. An error took place"); 
else if (polls) 
  console.log(polls); 
else 
  console.log("No results found dawg"); 
});
> 
> // Was published recently?
> Poll.findOne({question: /^Which do you usually/}, function(err, polls) { 
if (err) 
  console.log("Error took place."); 
else if (polls) 
  console.log(polls.wasPublishedRecently()); 
else console.log("No result"); 
});
> 
> Poll.findOne({question: /^Which do you usually/}, function(err, polls) { 
if (err) 
  console.log("Error took place."); 
else if (polls) 
  console.log(polls.wasPublishedRecently()); 
else 
  console.log("No result");
});

Continued in Part 2

This brings part 1 of the tutorial to an end. → Continue to part 2

If you have any questions, leave them in the comments below!

Comments