By liran bh | 7/24/2016 | JavaScript

MEAN Stack Tutorial - Part 2

After the basic setup, we start coding on the server side of the "stack" 

CODING THE SERVER SIDE

We need the add the logic to the server. We will be using Node.js and MongoDB for the server side and to start, we will go to the Category model because is the easiest.

Simple table with CategoryID, Name and Description 

We are only interested in the Category Name and Category description from this template. What we want to do is a CRUD (create, read, update and delete) API so, what we are going to see in this tutorial is how to interact with the DB, representing the data through a model, create a container for the logic and exposing the logic to the endpoint.

Now, let’s see what the CategoryAPI is intended to do:

Category API
 unauthenticated create request with
   valid category
     - returns success status 
     - returns category details including new id 
     - is saved in database 
   empty name
     - returns invalid status 
     - returns validation message 
   name longer than 15 chars in length
     - returns invalid status 
     - returns validation message 
   duplicate name
     - returns invalid status 
     - returns validation message 
 unauthenticated get request with
   no parameters
     - lists all categories in alphabetical order 
   valid category id
     - returns success status 
     - returns the expected category 
   invalid category id
     - returns not found status 
 unauthenticated update request with
   valid category
     - returns success status 
     - returns category details 
     - is updated in database 
     - only updates specified record 
   empty category name
     - returns invalid status 
     - returns validation message 
   category name longer than 15 chars in length
     - returns invalid status 
     - returns validation message 
   duplicate category name
     - returns invalid status 
     - returns validation message 
 unauthenticated delete request with
   valid category id
     - returns success status 
     - returns category details 
     - is deleted from database 
   invalid category id
     - returns not found status

 

Express Framework

We will use Express for the server framework in order to start and configure the server, configure the routes and route handlers for our logic and return static content. Express has some main files which are:

server.js- Grunt task calls this entry point.

config/express.js- This is where all the config is.

GETTING STARTED

Since there are a few things to do, we will do them one by one, first we will:

Add a Model

Models are more or less the same as classes in many programming languages and this one will represent the data of the Categories Database. In the category model shell run the following:

yo meanjs:express-model category

Some new files will be created. The app/models/category.server.model.js for the model and also the app/tests/category.server.model.test.js for the model test. So, the Category model will look like this now:

'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

/**
 * Category Schema
 */
var CategorySchema = new Schema({
    // Category model fields   
    // ... (we will add properties here soon...)
});

// add the model name and schema to the mongoose model.
mongoose.model('Category', CategorySchema);

By using the function require we are including the functionality of another module. This is the way to do it in Node.js.

As explained in the code, the line:

mongoose.model('Category', CategorySchema);

Will add the model name and schema to the mongoose instance and the model Category will be also available to any other modules when they use the word “require” mongoos

Testing the Model

Now we should run the test that we got previously. Use the command npm test to do it and the terminal will should perform it. This test should go through without problems but since there are no properties in the model, that’s not really useful.

Now go to app/tests/category.server.model.test.js and replace everything with the following snippet:

 

'use strict';

 

/**

 * Module dependencies.

 */

var should = require('should'),

       mongoose = require('mongoose'),

       Category = mongoose.model('Category');

 

/**

 * Unit tests

 */

describe('Category Model', function() {

 

       describe('Saving', function() {

              it('saves new record');

 

              it('throws validation error when name is empty');

 

              it('throws validation error when name longer than 15 chars');

             

              it('throws validation error for duplicate category name');

       });

 

});

Now by running the test again we will get the following messages:

Category Model

    Saving

      - saves new record

      - throws validation error when name is empty

      - throws validation error when name longer than 15 chars

      - throws validation error for duplicate category name

Next thing is to add properties to the model.

Working Out the Logic

We should add the properties to the model before implementing the tests. We will use the mongoose library to tinker with MongoDB.

Now copy the following snippet to app/models/category.server.model.js. In the same code you can also view the explanation of the most important lines.

'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
	Schema = mongoose.Schema,
	validation = require('./validation.server.model');

/**
 * Category Schema
 */
var CategorySchema = new Schema({
	created: {
		type: Date,
		default: Date.now
	},
	description: {
		type: String,
		default: '',
		trim: true
	},
	name: {
		type: String,
		default: '',
		trim: true, 	
		unique : true,
		required: 'name cannot be blank',
		validate: [validation.len(15), 'name must be 15 chars in length or less']
	}
});

mongoose.model('Category', CategorySchema);

And now paste the following to the app/tests/category.server.model.test.js and now the test will pass

/**
 * Module dependencies.
 */
var should = require('should'),
    mongoose = require('mongoose'),
    Category = mongoose.model('Category');

/**
 * Unit tests
 */
describe('Category Model', function() {

    describe('Saving', function() {
        it('saves new record', function(done) {
            var category = new Category({
                name: 'Beverages',
                description: 'Soft drinks, coffees, teas, beers, and ales'
            });

            category.save(function(err, saved) {
                should.not.exist(err);
                done();
            });
        });

        it('throws validation error when name is empty', function(done) {   
            var category = new Category({
                description: 'Soft drinks, coffees, teas, beers, and ales'
            });

            category.save(function(err) {
                should.exist(err);
                err.errors.name.message.should.equal('name cannot be blank');
                done();
            });
        });

        it('throws validation error when name longer than 15 chars', function(done) {
            var category = new Category({
                name: 'Grains/Cereals/Chocolates'
            });

            category.save(function(err, saved) {
                should.exist(err);
                err.errors.name.message.should.equal('name must be 15 chars in length or less');
                done();
            });
        });

        it('throws validation error for duplicate category name', function(done) {
            var category = new Category({
                name: 'Beverages'
            });

            category.save(function(err) {
                should.not.exist(err);

                var duplicate = new Category({
                    name: 'Beverages'
                });

                duplicate.save(function(err) {
                    err.err.indexOf('$name').should.not.equal(-1);
                    err.err.indexOf('duplicate key error').should.not.equal(-1);
                    should.exist(err);
                    done();
                });
            });
        });
    });

    afterEach(function(done) { 
        // NB this deletes ALL categories (but is run against a test database)
        Category.remove().exec();
        done();
    });
});

We have called a module “should” and that’s an assertion library. Should.js is a common choice to use with Mocha because this one doesn’t include one.

As long as the database is from Mongoose API we can save is calling .save().

 

Adding a Controller

We will create our controller to hold the logic and interact with our Category model using Express. First, we will generate the template for the controller with the yo generator.

yo meanjs:express-controller categories

Now, we can check out app/controllers/categories.server.controller.js and it will look like this:

'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
    _ = require('lodash');

/**
 * Create a Category
 */
exports.create = function(req, res) {

};

/**
 * Show the current Category
 */
exports.read = function(req, res) {

};

/**
 * Update a Category
 */
exports.update = function(req, res) {

};

/**
 * Delete an Category
 */
exports.delete = function(req, res) {

};

/**
 * List of Categories
 */
exports.list = function(req, res) {

};

This is a basic structure for our purpose but we still have to fill the blanks. But first we must create a route so that our controller can reach HTTP:

Adding a Route

First we must create it with yo. This is a express one.

yo meanjs:express-route categories

And now we can check out the file in app/routes/categories.server.routes.js

'use strict';

module.exports = function(app) {
	// Routing logic   
	// ...
};

 

Now we need it to output JSON to url http://localhost:3000/categories. Change the contents of the file to:

'use strict';

module.exports = function(app) {
	app.route('/categories')
		.get(function (request, response) {
			response.json([{ name: 'Beverages' }, { name: 'Condiments' }]);
		});
};

We can see now the arguments request and response. These come in the express and the object has the data from HTTP requests and the response object makes the controller change the state and then it is returned to the client.

If now we make a get request to http://localhost:3000/categories the output will be:

[

         {"name":"Beverages"},

         {"name":"Condiments"}

]

Ok, this is how all of this is working. We attached the route detail to the express framework instance and then when a request from HTTP GET is received for the url /categories this is the function used to handle the whole process. Since we plan to send the data in JSON format to the client we have called the function .json.

So, now we have a few parts of our API and we need to know how all of these parts work together. To put it simple, the route is in charge of the interactions with the HTTP (the web) but it doesn’t do anything with the model. The interactions with the model are made through the controller. There is a name for this structure, separation of concerns so, here is how each piece is working. The model is basically what it’s in the DB, the controller is the one in charge of any changes to the model and the route just handles the interactions with the web and passes them to the controller.

List Categories

We now need to take a look at the code in charge of the list of the categories for the list function in the controller. The next code is to be pasted into app/controllers/categories.server.controller.js:

'use strict';

/**
 * Module dependencies.
 */
var mongoose = require('mongoose'),
    errorHandler = require('./errors.server.controller'),
    Category = mongoose.model('Category'),
    _ = require('lodash');

/**
 * Create a Category
 */
exports.create = function(req, res) {

};

/**
 * Show the current Category
 */
exports.read = function(req, res) {

};

/**
 * Update a Category
 */
exports.update = function(req, res) {

};

/**
 * Delete an Category
 */
exports.delete = function(req, res) {

};

/**
 * List of Categories
 */
exports.list = function(req, res) {
    Category.find().exec(function(err, categories) {
        if (err) {
            return res.status(400).send({
                message: errorHandler.getErrorMessage(err)
            });
        } else {
            res.json(categories);
        }
    });
};

 

 And now we modify it so it interacts with the controller in app/routes/categories.server.routes.js:

'use strict';

module.exports = function(app) {
	var categories = require('../../app/controllers/categories.server.controller');

	app.route('/categories')
	  .get(categories.list);
};

We need to use the word require as a reference to the file path in order to include additional functionalities we need in our controller.

If we check the URL again at http://localhost:3000/categories we will just get an empty array because we still have nothing inside.

 

Creating Categories

Our list before was empty because we still haven’t created any. So, in order to create categories we have first to add the functionality required to do so. Now go to the controller and update the create method with the following code:

/**
 * Create a Category
 */
exports.create = function(req, res) {
	var category = new Category(req.body);

	category.save(function(err) {
		if (err) {
			return res.status(400).send({
				message: errorHandler.getErrorMessage(err)
			});
		} else {
			res.status(201).json(category);
		}
	});
};

Now we need to update the route so that it calls the create function we just made

'use strict';

module.exports = function(app) {
	var categories = require('../../app/controllers/categories.server.controller');

	app.route('/categories')
	  .get(categories.list)
	  .post(categories.create);
};

 And now it’s time for issuing a POST request to the endpoint with the JSON at http://localhost:3000/categories

{
	"name" : "Beverages",
	"description" : "Soft drinks, coffees, teas, beers, and ales"
}

This is a cURL POST that will come handy:

curl -H "Content-Type: application/json" -d '{"name" : "Beverages","description" : "Soft drinks, coffees, teas, beers, and ales"}' http://localhost:3000/categories

It may happen to some Windows users that they will get problems when using cURL with JSON so, it is better to use the Postman client and set the Content-Type header to application/json. This should solve the problem and you should get a response looking like the following:

{

         "__v":0,

         "_id":"54d2b9ca3c3113ca6fb9ba3b",

         "name":"Beverages",

         "description":"Soft drinks, coffees, teas, beers, and ales",

         "created":"2015-02-05T00:31:06.127Z"

}

If we now browse to our endpoint at http://localhost:3000/categories we will see the category we just created. To do so and if you use Google Chrome it is much better to use the Postman REST Client when we interact with the APIs.

To prevent update collisions Mongoose provides with the property __v. MongoDB generates the property _id. These are beyond the scope of this tutorial because we will need to explain what is sharding.

Getting the Category by ID

The next functionality is to get the category by ID. Go to the controller and paste the following snippet:

/**

 * Show the current Category

 */

exports.read = function(req, res) {

         Category.findById(req.params.categoryId).exec(function(err, category) {

                     if (err) {

                          return res.status(400).send({

                                 message: errorHandler.getErrorMessage(err)

                       });

      } else {

         if (!category) {

                          return res.status(404).send({

                                 message: 'Category not found'

                        });

          }

       res.json(category);

      }

   });

};

 

 

This is how Express works: In this case we are using the params object  attached to the request object to access the category ID. Now, we will set the categoryId configuration with the route:

'use strict';

 

module.exports = function(app) {

    var categories = require('../../app/controllers/categories.server.controller');

 

    app.route('/categories')

      .get(categories.list)

      .post(categories.create);

 

         // the categoryId param is added to the params object for the request

   app.route('/categories/:categoryId')

         .get(categories.read);

};

It is time to make a GET request at  http://localhost:3000/categories/54d2b9ca3c3113ca6fb9ba3b and we shall get the category by ID as it was planned.

More stuff to do

We have now the basic functionalities but still we have to implement Update and Delete functions. In order to do so, change the values of the following files accordingly.

Categories.server.controller.js

'use strict';

 	
/**

 	
 * Module dependencies.

 	
 */

 	
var mongoose = require('mongoose'),

 	
       errorHandler = require('./errors.server.controller'),

 	
       Category = mongoose.model('Category'),

 	
    _ = require('lodash');

 	
/**

 	
 * Create a Category

 	
 */

 	
exports.create = function(req, res) {
 	
       var category = new Category(req.body);
 	
       category.save(function(err) {
 	
              if (err) {
	
                      return res.status(400).send({
 	
                             message: errorHandler.getErrorMessage(err)
 	
                      });
 	
              } else {
                      res.status(201).json(category);
 	
              }
 	
       });
};

 	
/**

 	
 * Show the current Category

 	
 */

 	
exports.read = function(req, res) {
 	
       res.json(req.category);
 	
};

 	
/**
 	
 * Update a Category
 	
 */
 	
exports.update = function(req, res) {
 	
       var category = req.category;

       category = _.extend(category, req.body);
 	
       category.save(function(err) {
 	
              if (err) {
 	
                      return res.status(400).send({
 	
                             message: errorHandler.getErrorMessage(err)
 	
                      });
 	
              } else {
 	
                      res.json(category);
 	
              }
       });
};

 	
/**
 	
 * Delete an Category
 	
 */
 	
exports.delete = function(req, res) {
 	
       var category = req.category;
       category.remove(function(err) {
 	
              if (err) {
 	
                      return res.status(400).send({
                             message: errorHandler.getErrorMessage(err)
                      });
              } else {
                      res.json(category);
              }
       });
};

 	
/**	
 * List of Categories
 */

 	
exports.list = function(req, res) {
       Category.find().sort('name').exec(function(err, categories) {
              if (err) {
                      return res.status(400).send({
                             message: errorHandler.getErrorMessage(err)
                      });
              } else {
                      res.json(categories);
              }
       });
};


 	
/**	
 * Category middleware	
 */

exports.categoryByID = function(req, res, next, id) {
       if (!mongoose.Types.ObjectId.isValid(id)) { 	
              return res.status(400).send({
                      message: 'Category is invalid'
              });

       }
 	
       Category.findById(id).exec(function(err, category) {
              if (err) return next(err);
              if (!category) {
                      return res.status(404).send({
                             message: 'Category not found'
                      });

              }
              req.category = category;
              next();
 	
       });

};

 

Then categories.server.routes.js:

'use strict';




module.exports = function(app) {

	var categories = require('../../app/controllers/categories.server.controller');

	

	app.route('/categories')

		.get(categories.list)

		.post(categories.create);




	app.route('/categories/:categoryId')

		.get(categories.read)

		.put(categories.update)

		.delete(categories.delete);




	// Finish by binding the article middleware

	// What's this? Where the categoryId is present in the URL

	// the logic to 'get by id' is handled by this single function

	// and added to the request object i.e. request.category.

	app.param('categoryId', categories.categoryByID);

};

And finally, categories.server.model.js

'use strict';




/**

 * Module dependencies.

 */

var mongoose = require('mongoose'),

    Schema = mongoose.Schema;




/**

 * Validation

 */

function validateLength (v) {

  // a custom validation function for checking string length

  return v.length <= 15;

}




/**

 * Category Schema

 */

var CategorySchema = new Schema({

    created: {          // the property name

        type: Date,     // types are defined e.g. String, Date, Number - http://mongoosejs.com/docs/guide.html

        default: Date.now

    },

    description: {

        type: String,

        default: '',

        trim: true      // types have specific functions e.g. trim, lowercase, uppercase - http://mongoosejs.com/docs/api.html#schema-string-js

    },

    name: {

        type: String,

        default: '',

        trim: true,     

        unique : true,

        required: 'name cannot be blank',

        validate: [validateLength, 'name must be 15 chars in length or less'] // wires into our custom validator function - http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate

    }

});




// Expose the model to other objects (similar to a 'public' setter).

mongoose.model('Category', CategorySchema);

 

Here you are some cURL test scripts for your use. Remember to change the ID number by the one on your local machine.

For Update:

curl -H "Content-Type: application/json" -X PUT -d '{"name" : "Beverages","description" : "Soft drinks, coffees, teas, beers, wines and ales"}' http://localhost:3000/categories/54d82f5489b29df55a3c6124

To delete:

curl -H "Content-Type: application/json" -X DELETE http://localhost:3000/categories/54d82f5489b29df55a3c6124

Authentication

The meanjs template comes with some user’s functionality included with the logics for authorization and authentication. Take a look at them in /app/controllers/users. From this functionality we can use the logic to secure routes when users are not authenticated. To do this, we just have to change the category routes like in the following snippet:

'use strict';

module.exports = function(app) {
    var categories = require('../../app/controllers/categories.server.controller');
    var users = require('../../app/controllers/users.server.controller');

    app.route('/categories')
        .get(categories.list)
        .post(users.requiresLogin, categories.create);

    app.route('/categories/:categoryId')
        .get(categories.read)
        .put(users.requiresLogin, categories.update)
        .delete(users.requiresLogin, categories.delete);

    // Finish by binding the article middleware
    app.param('categoryId', categories.categoryByID);
};

Basically we changed it to add users.requiresLogin.

In the users.server.controller you can find the code for the method used:

/**
 * Require login routing middleware
 */
exports.requiresLogin = function(req, res, next) {
    if (!req.isAuthenticated()) {
        return res.status(401).send({
            message: 'User is not logged in'
        });
    }

    next();
};

We will call the function next() when the user is authenticated because this function is the method we use as category controller. So, if the user is not authenticated next() is not executed because the method of the controller returns and this secures it.

Middleware is an important part for the functionality to be put altogether in Express. Several layers of security, logging or other middleware can be in front of the controller for more security. Now, if you were wondering how the req.isAuthenticated is being set to true or false, the 3rd party library called Passport is the middleware in charge of the authentication for Node.js.

And this ends the server side logic of our API and now we move to the next part and tinker with the MongoDB basics.

Continue on Part 3

 

{{CommentsModel.TotalCount}} Comments

Your Comment

{{CommentsModel.Message}}

Recent Stories

Top DiscoverSDK Experts

User photo
3355
Ashton Torrence
Web and Windows developer
GUI | Web and 11 more
View Profile
User photo
3220
Mendy Bennett
Experienced with Ad network & Ad servers.
Mobile | Ad Networks and 1 more
View Profile
User photo
3060
Karen Fitzgerald
7 years in Cross-Platform development.
Mobile | Cross Platform Frameworks
View Profile
Show All
X

Compare Products

Select up to three two products to compare by clicking on the compare icon () of each product.

{{compareToolModel.Error}}

Now comparing:

{{product.ProductName | createSubstring:25}} X
Compare Now