Todo App with Express.js/Node.js and MongoDB

Note: This tutorial is a part of Express.js Guide: The Comprehensive Book on Express.js.

Todo apps are considered to be quintessential in showcasing frameworks akin to famous Todomvc.com for front-end JavaScript frameworks. In this example, we’ll use Jade, forms, LESS, AJAX/XHR and CSRF.

In our Todo app, we’ll intentionally not use Backbone.js or Angular to demonstrate how to build traditional websites with the use of forms and redirects. In addition to that, we’ll explain how to plug-in CSRF and LESS.

Example: All the source code is in the github.com/azat-co/todo-express for your convenience.

[Sidenote]

Reading blog posts is good, but watching video courses is even better because they are more engaging.

A lot of developers complained that there is a lack of affordable quality video material on Node. It's distracting to watch to YouTube videos and insane to pay $500 for a Node video course!

Go check out Node University which has FREE video courses on Node: node.university.

[End of sidenote]

Here are some screenshots of Todo app in which we start from a home page:

The Todo app home page.

There’s an empty list (unless you played with this app before):

Sidenote: If you like this post and interested in a corporate on-site JavaScript, Node.js and React.js training to boost productivity of your team, then contact NodeProgram.com.

The empty Todo List page.

Now we can add four items to the Todo List:

The Todo List page with added items.

Mark one of the tasks as “done”, e.g.. “Buy milk”:

The Todo List page with one item marked done.

Going to the Complete page reveals this done item:

The Todo app Completed page.

Deletion of an item from the Todo list is the only action performed via AJAX/XHR request. The rest of the logic is done via GETs and POSTs (by forms).

The Todo List page with a removed tasks.

Scaffolding

As usual, we start by running

$ express todo-express
$ cd todo-express
$ npm install

This will give us the basic Express.js application.

We’ll need to add two extra dependencies to package.json, the less-middleware and Mongoskin libraries:

$ npm install less-middleware --save
$ npm install mongoskin --save

Changing the name to todo-express is optional:

{
  "name": "todo-express",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.3.5",
    "jade": "*",
    "mongoskin": "~0.6.0",
    "less-middleware": "~0.1.12"
  }
}

MongoDB

Install MongoDB if you don’t have it already.

$ brew update
$ brew install mongodb
$ mongo --version

For more flavors of MongoDB installation, check out the official docs.

Structure

The final version of the app has the following folder/file structure (GitHub):

/todo-express
  /public
    /bootstrap
      *.less
    /images
    /javascripts
      main.js
      jquery.js
    /stylesheets
      style.css
      main.less
  /routes
    tasks.js
    index.js
  /views
    tasks_completed.jade
    layout.jade
    index.jade
    tasks.jade
  app.js
  readme.md
  package.json

The *.less in bootstrap folder means there are bunch of Twitter Bootstrap (the CSS framework) source files. They’re available at GitHub.

app.js

This is a break down of the Express.js generated app.js file with addition of routes, database, session, LESS and param middlewares.

Firstly, we import dependencies with Node.js global require() function:

var express = require('express');

Similarly, we get access to our own modules which are app’s routes:

var routes = require('./routes');
var tasks = require('./routes/tasks');

The core http and path modules will be needed as well:

var http = require('http');
var path = require('path');

Mongoskin is a better alternative to the native MongoDB driver:

var mongoskin = require('mongoskin');

One line is all we need to get the database connection object. The first param follows standard URI convention of protocol://username:password@host:port/database:

var db = mongoskin.db('mongodb://localhost:27017/todo?auto_reconnect', {safe:true});

The app itself:

var app = express();

In this middleware, we export the database object to all middlewares. By doing so, we’ll be able to perform database operations in the routes modules:

app.use(function(req, res, next) {
  req.db = {};

We just store the tasks collection in every request:

  req.db.tasks = db.collection('tasks');
  next();
})

This line allows us to access appname from within every jade template:

app.locals.appname = 'Express.js Todo App'

We set the server port to either the environment variable or if that’s undefined to 3000:

app.set('port', process.env.PORT || 3000);

These statements tell Express.js where templates live and what file extension to prepend in case the extension is omitted during the render calls:

app.set('views', __dirname + '/views');
app.set('view engine', 'jade');

Display Express.js favicon (the graphic in the URL address bar of browsers):

app.use(express.favicon());

Out-of-the-box logger will print requests in the terminal window:

app.use(express.logger('dev'));

The bodyParser() middleware is needed for painlessly accessing incoming data:

app.use(express.bodyParser());

The methodOverride() middleware is a work around for HTTP methods that involve headers. It’s not essential for this example, but we’ll leave it here:

app.use(express.methodOverride());

To use CSRF, we need cookieParser() and session():

app.use(express.cookieParser());
app.use(express.session({
  secret: '59B93087-78BC-4EB9-993A-A61FC844F6C9'
}));

The csrf() middleware itself. The order is important; in other words, csrf() must be preceded by cookieParser() and session():

app.use(express.csrf());

To process LESS stylesheets into CSS ones, we utilize less-middleware in this manner:

app.use(require('less-middleware')({
  src: __dirname + '/public', 
  compress: true 
}));

The other static files are also in the public folder:

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

Remember CSRF? This is how we expose it to templates:

app.use(function(req, res, next) {
  res.locals._csrf = req.session._csrf;
  return next();
})

The router plug-in is enabled by this statement. It’s important to have this line after less-middleware and csrf() lines above:

app.use(app.router);

It’s possible to configure different behavior based on environments:

if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

When there’s a request that matches route/RegExp with :task_id in it, this block is executed:

app.param('task_id', function(req, res, next, taskId) {

The value of task ID is in taskId and we query the database to find that object:

  req.db.tasks.findById(taskId, function(error, task){

It’s very important to check for errors and empty results:

    if (error) return next(error);
    if (!task) return next(new Error('Task is not found.'));

If there’s data, store it in the request and proceed to next middleware:

    req.task = task;
    return next();
  });
});

Now it’s time to define our routes. We start with home page:

app.get('/', routes.index);

The Todo List page:

app.get('/tasks', tasks.list);

This route will mark all tasks in the todo list as completed if the user presses all done button. In a RESP API, the HTTP method would be PUT but because we’re building classical web apps with forms, we have to use POST:

app.post('/tasks', tasks.markAllCompleted)

The same URL for adding new tasks as for marking all tasks completed, but in the previous methods itself (markAllCompleted) you’ll see how we handle flow control:

app.post('/tasks', tasks.add);

To mark a single task completed, we use aforementioned :task_id string in our URL pattern. In REST API, this should have been a PUT request:

app.post('/tasks/:task_id', tasks.markCompleted);

Unlike with the POST route above, we utilize Express.js param middleware with :task_id token:

app.del('/tasks/:task_id', tasks.del);

For our Completed page, we define this route:

app.get('/tasks/completed', tasks.completed);

In case of malicious attacks or mistyped URLs, it’s a user-friendly thing to catch all requests with *. Keep in mind that if we had a match previously, the Node.js won’t come to execute this block:

app.all('*', function(req, res){
  res.send(404);
})

Finally, we spin up our application with good ’ol http method:

http.createServer(app).listen(app.get('port'), 
  function(){
    console.log('Express server listening on port '
      + app.get('port'));
  }
);

The full content of app.js file:


/**
 * Module dependencies.
 */

var express = require('express');
var routes = require('./routes');
var tasks = require('./routes/tasks');
var http = require('http');
var path = require('path');
var mongoskin = require('mongoskin');
var db = mongoskin.db('mongodb://localhost:27017/todo?auto_reconnect', {safe:true});
var app = express();
app.use(function(req, res, next) {
  req.db = {};
  req.db.tasks = db.collection('tasks');
  next();
})
app.locals.appname = 'Express.js Todo App'
// all environments

app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({secret: '59B93087-78BC-4EB9-993A-A61FC844F6C9'}));
app.use(express.csrf());

app.use(require('less-middleware')({ src: __dirname + '/public', compress: true }));
app.use(express.static(path.join(__dirname, 'public')));
app.use(function(req, res, next) {
  res.locals._csrf = req.session._csrf;
  return next();
})
app.use(app.router);

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}
app.param('task_id', function(req, res, next, taskId) {
  req.db.tasks.findById(taskId, function(error, task){
    if (error) return next(error);
    if (!task) return next(new Error('Task is not found.'));
    req.task = task;
    return next();
  });
});

app.get('/', routes.index);
app.get('/tasks', tasks.list);
app.post('/tasks', tasks.markAllCompleted)
app.post('/tasks', tasks.add);
app.post('/tasks/:task_id', tasks.markCompleted);
app.del('/tasks/:task_id', tasks.del);
app.get('/tasks/completed', tasks.completed);

app.all('*', function(req, res){
  res.send(404);
})
http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

Routes

There are only two files in routes folder. One of them serves the home page (e.g., http://localhost:3000/) and is straightforward:


/*
 * GET home page.
 */

exports.index = function(req, res){
  res.render('index', { title: 'Express.js Todo App' });
};

The remaining logic that deals with tasks itself has been placed in the todo-express/routes/tasks.js. Let’s break it down a bit.

We start by exporting list() request handler that gives us list of incomplete tasks:

exports.list = function(req, res, next){

To do so, we perform a database search with completed=false query:

  req.db.tasks.find({
    completed: false
  }).toArray(function(error, tasks){

In the callback, we need to check for any errors:

    if (error) return next(error);

Since we use toArray(), we can send the date directly to the template:

    res.render('tasks', {
      title: 'Todo List',
      tasks: tasks || []
    });
  });
};

Adding a new task requires us to check for the name parameter:

exports.add = function(req, res, next){
  if (!req.body || !req.body.name) 
    return next(new Error('No data provided.'));

Thanks to our middleware, we already have a database collection in the req object, and the default value for the task is incomplete (completed: false):

  req.db.tasks.save({
    name: req.body.name,
    completed: false
  }, function(error, task){

Again, it’s important to check for errors and propagate them with Express.js next() function:

    if (error) return next(error);
    if (!task) return next(new Error('Failed to save.'));

The logging is optional; however, it’s useful for learning and debugging:

    console.info('Added %s with id=%s', task.name, task._id);

Lastly, we redirect back to the Todo List page when the saving operation is finished successfully:

    res.redirect('/tasks');
  })
};

This method marks all incomplete tasks as complete:

exports.markAllCompleted = function(req, res, next) {

Because we had to re-use POST route and since it’s a good illustration of flow control, we check for the all_done parameter to decide if this request comes from the all done button or the add button:

  if (!req.body.all_done 
    || req.body.all_done !== 'true') 
    return next();

If the execution come this far, we perform db query with multi: true:

  req.db.tasks.update({
    completed: false
  }, {$set: {
    completed: true
  }}, {multi: true}, function(error, count){

Significant error handling, logging and redirection back to Todo List page:

    if (error) return next(error);
    console.info('Marked %s task(s) completed.', count);
    res.redirect('/tasks');
  })
};

The Completed route is akin to Todo List except for the completed flag value (true in this case):

exports.completed = function(req, res, next) {
  req.db.tasks.find({
    completed: true
  }).toArray(function(error, tasks) {
    res.render('tasks_completed', {
      title: 'Completed',
      tasks: tasks || []
    });
  });
};

This is the route that takes care of marking a single task as done. We use updateById but the same thing can be accomplished with a plain update method from Mongoskin/MongoDB API. The trick with completed: req.body.completed === 'true is needed because the incoming value is a string and not a boolean.

exports.markCompleted = function(req, res, next) {
  if (!req.body.completed) 
    return next(new Error('Param is missing'));
  req.db.tasks.updateById(req.task._id, {
    $set: {completed: req.body.completed === 'true'}},
    function(error, count) {

Once more, we perform error and results check (update() and updateById() don’t return object, but the count of affected documents instead):

      if (error) return next(error);
      if (count !==1) 
        return next(new Error('Something went wrong.'));
      console.info('Marked task %s with id=%s completed.', 
        req.task.name, 
        req.task._id);
      res.redirect('/tasks');
    }
  )
}

Delete is the single route called by an AJAX request. However, there’s nothing special about its implementation. The only difference is that we don’t redirect, but send status 200 back.

Just for your information, the remove() method can be used instead of removeById().

exports.del = function(req, res, next) {
  req.db.tasks.removeById(req.task._id, function(error, count) {
    if (error) return next(error);
    if (count !==1) return next(new Error('Something went wrong.'));
    console.info('Deleted task %s with id=%s completed.', 
      req.task.name, 
      req.task._id);
    res.send(200);
  });
}

For your convenience, here’s the full content of the todo-express/routes/tasks.js file:


/*
 * GET users listing.
 */

exports.list = function(req, res, next){
  req.db.tasks.find({completed: false}).toArray(function(error, tasks){
    if (error) return next(error);
    res.render('tasks', {
      title: 'Todo List',
      tasks: tasks || []
    });
  });
};

exports.add = function(req, res, next){
  if (!req.body || !req.body.name) return next(new Error('No data provided.'));
  req.db.tasks.save({
    name: req.body.name,
    completed: false
  }, function(error, task){
    if (error) return next(error);
    if (!task) return next(new Error('Failed to save.'));
    console.info('Added %s with id=%s', task.name, task._id);
    res.redirect('/tasks');
  })
};

exports.markAllCompleted = function(req, res, next) {
  if (!req.body.all_done || req.body.all_done !== 'true') return next();
  req.db.tasks.update({
    completed: false
  }, {$set: {
    completed: true
  }}, {multi: true}, function(error, count){
    if (error) return next(error);
    console.info('Marked %s task(s) completed.', count);
    res.redirect('/tasks');
  })
};

exports.completed = function(req, res, next) {
  req.db.tasks.find({completed: true}).toArray(function(error, tasks) {
    res.render('tasks_completed', {
      title: 'Completed',
      tasks: tasks || []
    });
  });
};

exports.markCompleted = function(req, res, next) {
  if (!req.body.completed) return next(new Error('Param is missing'));
  req.db.tasks.updateById(req.task._id, {$set: {completed: req.body.completed === 'true'}}, function(error, count) {
    if (error) return next(error);
    if (count !==1) return next(new Error('Something went wrong.'));
    console.info('Marked task %s with id=%s completed.', req.task.name, req.task._id);
    res.redirect('/tasks');
  })
};

exports.del = function(req, res, next) {
  req.db.tasks.removeById(req.task._id, function(error, count) {
    if (error) return next(error);
    if (count !==1) return next(new Error('Something went wrong.'));
    console.info('Deleted task %s with id=%s completed.', req.task.name, req.task._id);
    res.send(200);
  });
};

Jades

In the Todo app, we use four templates:

  • layout.jade: the skeleton of HTML pages that is used on all pages
  • index.jade: home page
  • tasks.jade: Todo List page
  • tasks_completed.jade: Completed page

Let’s go through each file starting with layout.jade. It starts with doctype, html and head types:

doctype 5
html
  head

We should have appname variable set:

    title= title + ' | ' + appname

Next we include *.css files but underneath, Express.js will serve its contents from LESS files:

    link(rel="stylesheet", href="/stylesheets/style.css")
    link(rel="stylesheet", href="/bootstrap/bootstrap.css")
    link(rel="stylesheet", href="/stylesheets/main.css")

The body with Twitter Bootstrap structure consist of .container and .navbar. To read more about those and other classes, go to getbootstrap.com/css/:

  body
    .container
      .navbar.navbar-default
        .container
          .navbar-header
            a.navbar-brand(href='/')= appname
      .alert.alert-dismissable
      h1= title
      p Welcome to Express.js Todo app by 
        a(href='http://twitter.com/azat_co') @azat_co
        |. Please enjoy.

This is the place where other jades (like tasks.jade) will be imported:

      block content

The last lines include front-end JavaScript files:

  script(src='/javascripts/jquery.js', type="text/javascript")
  script(src='/javascripts/main.js', type="text/javascript")

The full layout.jade file:

doctype 5
html
  head
    title= title + ' | ' + appname
    link(rel="stylesheet", href="/stylesheets/style.css")
    link(rel="stylesheet", href="/bootstrap/bootstrap.css")
    link(rel="stylesheet", href="/stylesheets/main.css")

  body
    .container
      .navbar.navbar-default
        .container
          .navbar-header
            a.navbar-brand(href='/')= appname
      .alert.alert-dismissable
      h1= title
      p Welcome to Express.js Todo app by 
        a(href='http://twitter.com/azat_co') @azat_co
        |. Please enjoy.
      block content
  script(src='/javascripts/jquery.js', type="text/javascript")
  script(src='/javascripts/main.js', type="text/javascript")

The index.jade file is our home page and it’s quite vanilla. The most interesting thing it had is the nav-pills menu:

extends layout

block content
  .menu
    h2 Menu
    ul.nav.nav-pills
      li.active
        a(href="/tasks") Home
      li
        a(href="/tasks") Todo List
      li
        a(href="/tasks") Completed
  .home
    p This is an example of classical (no front-end JavaScript frameworks) web application built with Express.js 3.3.5 for 
      a(href="http://expressjsguide.com") Express.js Guide
      |.
    p The full source code is available at 
      a(href='http://github.com/azat-co/todo-express') github.com/azat-co/todo-express
      |.

The tasks.jade uses extends layout:

extends layout

block content

Then goes our main page specific content:

  .menu
    h2 Menu
    ul.nav.nav-pills
      li
        a(href='/') Home
      li.active
        a(href='/tasks') Todo List
      li
        a(href="/tasks/completed") Completed
  h1= title

The div with list class will hold the Todo List:

  .list
    .item.add-task

The form to mark all items as done has CSRF token in a hidden field and uses POST method pointed to /tasks:

      div.action
        form(action='/tasks', method='post')
          input(type='hidden', value='true', name='all_done')
          input(type='hidden', value=locals._csrf, name='_csrf')
          input(type='submit', class='btn btn-success btn-xs', value='all done')

Similar CSRF enabled form is for new task creation:

      form(action="/tasks", method='post')
        input(type='hidden', value=locals._csrf, name='_csrf')
        div.name
          input(type="text", name="name", placeholder='Add a new task')
        div.delete
          input.btn.btn-primary.btn-sm(type="submit", value='add')

When we start the app for the first time (or clean the database), there are no tasks:

    if (tasks.length === 0)
      | No tasks.

Jade supports iterations with each command:

    each task, index in tasks
      .item
        div.action

This form submits data to individual task route:

          form(action='/tasks/#{task._id}', method='post')
            input(type='hidden', value=task._id.toString(), name='id')
            input(type='hidden', value='true', name='completed')
            input(type='hidden', value=locals._csrf, name='_csrf')
            input(type='submit', class='btn btn-success btn-xs task-done', value='done')

The index variable is used to display order in the list of tasks:

        div.num
          span=index+1
            |. 
        div.name
          span.name=task.name
          //- no support for DELETE method in forms
          //- http://amundsen.com/examples/put-delete-forms/
          //- so do XHR request instead from public/javascripts/main.js

The delete button doesn’t have anything fancy attached to it, because events are attached to these buttons from main.js front-end JavaScript file:

        div.delete
          a(class='btn btn-danger btn-xs task-delete', data-task-id=task._id.toString(), data-csrf=locals._csrf) delete

The full source code of tasks.jade:

extends layout

block content

  .menu
    h2 Menu
    ul.nav.nav-pills
      li
        a(href='/') Home
      li.active
        a(href='/tasks') Todo List
      li
        a(href="/tasks/completed") Completed
  h1= title

  .list
    .item.add-task
      div.action
        form(action='/tasks', method='post')
          input(type='hidden', value='true', name='all_done')
          input(type='hidden', value=locals._csrf, name='_csrf')
          input(type='submit', class='btn btn-success btn-xs', value='all done')
      form(action="/tasks", method='post')
        input(type='hidden', value=locals._csrf, name='_csrf')
        div.name
          input(type="text", name="name", placeholder='Add a new task')
        div.delete
          input.btn.btn-primary.btn-sm(type="submit", value='add')
    if (tasks.length === 0)
      | No tasks.
    each task, index in tasks
      .item
        div.action
          form(action='/tasks/#{task._id}', method='post')
            input(type='hidden', value=task._id.toString(), name='id')
            input(type='hidden', value='true', name='completed')
            input(type='hidden', value=locals._csrf, name='_csrf')
            input(type='submit', class='btn btn-success btn-xs task-done', value='done')
        div.num
          span=index+1
            |. 
        div.name
          span.name=task.name
          //- no support for DELETE method in forms
          //- http://amundsen.com/examples/put-delete-forms/
          //- so do XHR request instead from public/javascripts/main.js
        div.delete
          a(class='btn btn-danger btn-xs task-delete', data-task-id=task._id.toString(), data-csrf=locals._csrf) delete

Last but not least, comes tasks_completed.jade which is just a striped down version of tasks.jade file:

extends layout

block content

  .menu
    h2 Menu
    ul.nav.nav-pills
      li
        a(href='/') Home
      li
        a(href='/tasks') Todo List
      li.active
        a(href="/tasks/completed") Completed

  h1= title

  .list
    if (tasks.length === 0)
      | No tasks.
    each task, index in tasks
      .item
        div.num
          span=index+1
            |. 
        div.name.completed-task
          span.name=task.name

LESS

As we’ve mentioned before, after applying proper middleware in app.js files, we can put *.less files anywhere under public folder. Express.js works by accepting request for some .css file and tries to match corresponding file by name. Therefore, we include *.css files in our jades.

Here is the content of the todo-express/public/stylesheets/main.less file:

* {
  font-size:20px;
}
.btn {
  // margin-left: 20px;
  // margin-right: 20px;
}
.num {
  // margin-right: 3px;
}
.item {
  height: 44px;
  width: 100%;
  clear: both;
  .name {
    width: 300px;
  }
  .action {
    width: 100px;
  }
  .delete {
    width: 100px
  }
  div {
    float:left;
  }
}
.home {
  margin-top: 40px;
}
.name.completed-task {
  text-decoration: line-through;
}

Conclusion

The Todo app is considered classical because it doesn’t rely on any front-end framework. This was done intentionally to show how easy it is to use Express.js for such tasks. In modern day development, people often leverage some sort of REST API server architecture with front-end client built with Backbone.js, Angular, Ember or something else. Next examples dive into details about how to write such servers.

If you found this tutorial helpful, please take a look at Express.js Guide book, in which there many similar examples of Node.js developement.

--
Azat Mardan
Azat Mardan avatar
https://www.linkedin.com/in/azatm
To contact Azat, the main author of this blog, submit the contact form.

Also, make sure to get 3 amazing resources to FREE when you sign up for the newsletter.
Simple.
Easy.
No commitment.

15 thoughts on “Todo App with Express.js/Node.js and MongoDB

  1. Cara Memutihkan Wajah

    Hi would you mind letting me know which webhost you’re working with?
    I’ve loaded your blog in 3 completely different web
    browsers and I must say this blog loads a lot quicker then most.
    Can you recommend a good web hosting provider at a reasonable price?

    Thank you, I appreciate it!

  2. Prashish Jain

    I am getting this error on terminal when I am running the server:

    unable to connect to database at mongodb://localhost/todo-list-development

  3. Anna

    Hi Azat,

    I was wondering if the book shows how to use more than one collection of documents, from the mongoDB.
    And if it has any information about how to work with user, like verifying if the user exist.

    Thanks for the tutorial, it is really helpful.

  4. Guru Inamdar

    Thank you for such a detailed example with less-middleware. I have one question though

    I was running this example with express4 and I found that if I have this route
    app.get(‘/tasks/completed’, tasks.completed); after
    app.post(‘/tasks/:task_id’, tasks.markCompleted);
    app.del(‘/tasks/:task_id’, tasks.del);

    then task/completed never works (it works with your source code)

    but i have to run it like this to make it work

    app.get(‘/’, routes.index);
    app.get(‘/tasks’, tasks.list);
    app.post(‘/tasks’, tasks.markAllCompleted);
    app.post(‘/tasks’, tasks.add);
    app.get(‘/tasks/completed’, tasks.completed);
    app.post(‘/tasks/:task_id’, tasks.markCompleted);
    app.del(‘/tasks/:task_id’, tasks.del);

    1) Is this issue with express4?
    2) I also noticed that
    app.param(‘task_id’, function(req, res, next, taskId) is being called twice and again not sure why.

    These are my packe.json dependency
    “express”: “~4.2.0”,
    “static-favicon”: “~1.0.0”,
    “morgan”: “~1.0.0”,
    “cookie-parser”: “~1.0.1”,
    “body-parser”: “~1.0.0”,
    “debug”: “~0.7.4”,
    “jade”: “~1.3.0”,
    “less-middleware”: “^1.0.3”,
    “mongoskin”: “^1.4.4”,
    “method-override”: “^1.0.2”,
    “csrf”: “0.0.3”,
    “express-session”: “^1.2.0”,
    “http”: “0.0.0”,
    “errorhandler”: “^1.0.1”,

  5. Pingback: 10 Node.Js Example App + Tutorial for 2014 - Fresh Web Dev

  6. Azat Post author

    1) No, it’s a front end file, we manually create it and put in /public/… you can look it up in the GitHub repo
    2) Maybe, usually it’s just a zip download and again putting under /public

    PS: Don’t confuse front-end and back end js :-)
    Although with npm it’s possible to install front-end modules. You should check out ender.js as well.

  7. ZZ

    Hi Azat,

    Thanks for this great tutorial. Should main.js be automatically generated in /public/javascripts? I noticed that you don’t cover the code for that file here.

    Also, is there an automatic way to install /bootstrap/*.less in /public? I know we can do
    npm install bootstrap --save
    but this only installs bootstrap into /node-modules.
    Similar question for /public/javascripts/jquery.js — is there a way to install this automatically?

  8. Azat Post author

    Hi Lukas, thank you for reading. Yes, the default laws apply. However, if you plan to use my work (code or writing), we can come up with a special agreement. Just send me an email/message with your particular use case. For example, I encourage people to conduct Node.js and Backbone.js training with my tutorials. I can setup a special price for ebooks and paperbacks for that.

  9. Lukas Lipavsky

    Thanks for an excellent article! I really really considering buying book now ;-)

    One request though. I do not see any license/copyright notice. Can you add some so I know whether I can use code parts directly or not?

    A longer explanation is on github (for example). The short citation:


    You’re under no obligation to choose a license. It’s your right not to include one with your code or project, but please be aware of the implications. Generally speaking, the absence of a license means that the default copyright laws apply. This means that you retain all rights to your source code and that nobody else may reproduce, distribute, or create derivative works from your work. This might not be what you intend.

    This applies both to your code in github and to this blogpost…

    Thanks in advance

  10. Pingback: Installing the MEAN Stack on Windows Vista | Matthew R Monroe

Leave a Reply

Your email address will not be published. Required fields are marked *