Today we’ll be making a simple todo application based of the TodoMVC http://todomvc.com/architecture-examples/angularjs/#/ project, using the MEAN (Mongo, Express, Angular, Node) Stack. We will:

Create a restful API

Store todos in a MongoDB

Use AngularJS as a frontend

Interface with our API through ng-resource

Use polling to sync todos across clients

What we’re building

This is the working app. The full source code is available here: https://github.com/DaftMonk/mean-todomvc

Getting Started

If you haven’t used the angular-fullstack generator before, have a look at a previous article. You’ll also need to have MongoDB installed to follow along.

Lets set up a new project. In a new directory, run yo angular-fullstack . Then use the following configuration:

[?] Would you like to use Sass (with Compass)? No [?] Would you like to include Twitter Bootstrap? No [?] Which modules would you like to include? angular-resource.js, angular-route.js [?] Would you like to include MongoDB with Mongoose? Yes [?] Would you like to include a Passport authentication boilerplate? No

When thats done, run grunt serve . If everything is working, you should see a welcome message with a list of some of the packages your project is using. It won’t have much default styling because we’re not using bootstrap, don’t worry, we’ll take care of that later.

Starting with a clean slate

Let’s just clear away some stuff we won’t need. Delete the following files

lib/config/dummydata.js lib/models/thing.js lib/controllers/api.js

Edit server.js and remove require('./lib/config/dummydata'); .

We’ll also want to delete references to the api controller from lib/routes .

// lib/routes ... api = require('./controllers/api'), // remove ... app.get('/api/awesomeThings', api.awesomeThings); // remove

The Todo API

Lets start by creating the schema for our Todo model.

// lib/models/todo.js var mongoose = require('mongoose'), Schema = mongoose.Schema; /** * Todo Schema */ var TodoSchema = new Schema({ title: String, completed: Boolean, createdAt: Date, updatedAt: Date, }); // keep track of when todos are updated and created TodoSchema.pre('save', function(next, done){ if (this.isNew) { this.createdAt = Date.now(); } this.updatedAt = Date.now(); next(); }); mongoose.model('Todo', TodoSchema);

We’re keeping track of when our todos are created and updated so that we can sort by that later.

Now lets go ahead and make the todo controller. The todo controller is where all our CRUD actions will take place.

// lib/controllers/todos.js var mongoose = require('mongoose'), Todo = mongoose.model('Todo'); /** * Find todo by id and store it in the request */ exports.todo = function(req, res, next, id) { Todo.findById(id, function(err, todo) { if (err) return next(err); if (!todo) return next(new Error('Failed to load todo ' + id)); req.todo = todo; next(); }); }; /** * List of todos */ exports.query = function(req, res) { Todo.find().sort('-createdAt').exec(function(err, todos) { if (err) return res.json(500, err); res.json(todos); }); }; /** * Show a todo */ exports.show = function(req, res) { res.json(req.todo); }; /** * Create a todo */ exports.create = function(req, res) { var todo = new Todo(req.body); todo.save(function(err) { if (err) return res.json(500, err); res.json(todo); }); }; /** * Update a todo */ exports.update = function(req, res) { Todo.update({ _id: req.todo._id }, req.body, { }, function(err, updatedTodo) { if (err) return res.json(500, err); res.json(updatedTodo); }); }; /** * Remove a todo */ exports.remove = function(req, res) { var todo = req.todo; todo.remove(function(err) { if (err) return res.json(500, err); res.json(todo); }); };

The CRUD operations are pretty self explanatory, they’re just using standard mongoose methods.

One thing that is more important to note is the todo method. We’ll talk more about this in a moment, but any request for a specific todoId will fire this method off first, which allows us to find the requested todo and store it in req.todo for any other request handlers to use.

Routes

Lets create the Express routes to handle our todo API calls.

// lib/routes.js 'use strict'; var index = require('./controllers'), todos = require('./controllers/todos'); ... // Server API Routes app.param('todoId', todos.todo); app.post('/api/todos', todos.create); app.get('/api/todos', todos.query); app.get('/api/todos/:todoId', todos.show); app.put('/api/todos/:todoId', todos.update); app.del('/api/todos/:todoId', todos.remove); ...

app.param lets us execute the todos.todo method any time a todoId is present in the route path. This lets us set req.todo as we mentioned above.

Our api is done. If you go to localhost:8000/api/todos you’ll see a list of our todos, which is currently empty.

The client interface

Now that the server side is finished, lets install the todomvc-common bower package which contains all the styles we’ll need.

bower install --save todomvc-common

Most bower packages will specify a main file, which you can automatically link to your index file with grunt bower-install . However with this particular package, we have to link it manually.

...

Now we’ll add the html for our main view. Jump into your views/partials/main.html and replace the contents with

todos Mark all as complete {{todo.title}}

Some of the things this will do:

Creates a form thats we use to add todos

Create an input to mark all todos as completed

Display all todos, and allow you to edit a todo by double clicking, or delete it by clicking x button

button Auto focus the current edited todo, and allow you to cancel editing by pressing escape.

Allow you to filter by active / complete / all todos by changing the route

We’ll have to implement most of that in the controller, we’ll get to that last.

Custom directives

We need to create the two custom directives we’re using in the main view, so lets scaffold them.

yo angular-fullstack:directive todoFocus yo angular-fullstack:directive todoEscape

The focus directive is used to focus the todo which is currently being edited

// app/scripts/directives/todofocus.js ... .directive('todoFocus', function todoFocus($timeout) { return function (scope, elem, attrs) { scope.$watch(attrs.todoFocus, function (newVal) { if (newVal) { $timeout(function () { elem[0].focus(); }, 0, false); } }); }; });

And this directive is used to revert editing of the todo when the escape key is pressed

// app/scripts/directives/todoescape.js ... .directive('todoEscape', function () { var ESCAPE_KEY = 27; return function (scope, elem, attrs) { elem.bind('keydown', function (event) { if (event.keyCode === ESCAPE_KEY) { scope.$apply(attrs.todoEscape); } }); }; });

Todo resource

This factory will return a new $resource object configured for the todo api.

// app/scripts/services/todo.js ... .factory('Todo', function ($resource) { return $resource('api/todos/:todoId', { todoId: '@_id' }, { update: { method: 'PUT' } }); });

The second argument of the $resource factory is an object that lets us set the default parameters.

Setting todoId to @_id will set todoId to the _id value of the current data object. This will make it easy for us to update and delete existing todos.

The $resource object comes out of the box with save , query , get , and delete methods, but as it’s lacking an update method we add our own which uses PUT ,

App.js

We want to set the current filter based on the route, eg /active for the active filter. However, a route change normally will either take you to another controller, or reload the current controller all together. That doesn’t look so good because controller reload clears the todo array and has to download todos from the server again.

Fortunately, we can make an exception to reloading the current controller by using search params. Then we can make route changes like ?q=active without triggering a reload. Add the following configuration to our route.

// app/scripts/app.js ... .when('/', { templateUrl: 'partials/main', controller: 'MainCtrl', reloadOnSearch: false }) ...

The Main Controller

The final bit that ties it all together is the main controller. Edit the current main controller and replace it with the following.

// app/scripts/controllers/main.js ... .controller('MainCtrl', function ($scope, $timeout, Todo, filterFilter, $location) { $scope.todos = []; $scope.newTodo = ''; $scope.editedTodo = null; // set the filter status to the initial search query if it exists $scope.status = $location.search().q || ''; // watch the todos array for changes and update the counts $scope.$watch('todos', function () { $scope.remainingCount = filterFilter($scope.todos, { completed: false }).length; $scope.completedCount = $scope.todos.length - $scope.remainingCount; $scope.allChecked = !$scope.remainingCount; }, true); // monitor the current location for changes and adjust the filter accordingly $scope.$on('$locationChangeSuccess', function () { var status = $scope.status = $location.search().q || ''; $scope.statusFilter = (status === 'active') ? { completed: false } : (status === 'completed') ? { completed: true } : null; }); // create a new todo locally save it remotely $scope.addTodo = function () { var todoTitle = $scope.newTodo.trim(); if (!todoTitle.length) { return; } var newTodo = new Todo({ title: todoTitle, completed: false }); newTodo.$save(); $scope.todos.unshift(newTodo); $scope.newTodo = ''; }; // remove todo locally and remotely $scope.removeTodo = function (id) { $scope.todos[id].$remove(); $scope.todos.splice(id, 1); }; // begin editing a todo, save the original in case of cancel $scope.editTodo = function (id) { $scope.editedTodo = $scope.todos[id]; $scope.originalTodo = angular.extend({}, $scope.editedTodo); }; // update when done editing, or if title is erased remove the todo $scope.doneEditing = function (id) { $scope.editedTodo = null; var title = $scope.todos[id].title.trim(); if (title) { $scope.todos[id].$update(); } else { $scope.removeTodo(id); } }; // revert the edited todo back to what it was $scope.revertEditing = function (id) { $scope.todos[id] = $scope.originalTodo; $scope.doneEditing(id); }; // toggle todo completed, and update remotely $scope.toggleCompleted = function (id) { var todo = $scope.todos[id]; todo.completed = !todo.completed; todo.$update(); }; // remove completed todos locally and from server $scope.clearCompletedTodos = function () { var remainingTodos = []; angular.forEach($scope.todos, function (todo) { if (todo.completed) { todo.$remove(); } else { remainingTodos.push(todo); } }); $scope.todos = remainingTodos; }; // mark all as completed or not, then update remotely $scope.markAll = function (allCompleted) { angular.forEach($scope.todos, function (todo) { todo.completed = !allCompleted; todo.$update(); }); }; // Poll server to regularly update todos (function refreshTodos() { Todo.query(function(response) { // Update todos if a todo is not being edited if($scope.editedTodo === null) { $scope.todos = response; } $scope.promise = $timeout(refreshTodos, 5000); }); })(); // when the controller is destroyed, cancel the polling $scope.$on('destroy', function(){ $timeout.cancel($scope.promise); }); });

We use the $resource methods, which are attached to each of the todo objects in our todo array, to keep everything synced up on the server side.

And that’s it for everything! You should now be able to launch the server, and edit todos in two different screens and have them sync up every 5 seconds when they poll the server for changes.

Conclusion

We’ve built a fully working full stack todo application that lets us do CRUD operations on a todo list! An overview of what we’ve built and accomplished.

A restful API with express

A single page app that requires no refresh

Used mongoDB as our database through mongoose

Used $resource to interface with our API

Going forward you may also find times you want to use socket.io, rather than polling or simple api requests. Have a look at my MEAN chat app to get some ideas for ways you could set that up: https://github.com/DaftMonk/mean-chat