Repository
https://github.com/nodejs/node
What Will I Learn
This is the fourth tutorial in the series where I am documenting and sharing the process of building an ecommerce application using MongoDB, Express, AngularJS and NodeJS.
The code for the entire series is based on the book Web Application Development with MEAN by Adrian Mejia
In the last tutorial I covered the config directory and how it works, including all configuration modules in the directory.
In this tutorial I would be working on the API for the application modules and discussing how each application feature interacts with the database.
The application has four main API modules as listed below
- Catalog API
- Order API
- Product API
- User API
Each API listed above includes the following module helping to perform the various required tasks.
- Model
- Controller
- Events
- Integration
- Socket
In this tutorial I will cover the catalog while the remaining will be treated in a later tutorial.
Requirements
- NodeJS and NPM,
- Angular
- MongoDB
- Code Editor
Difficulty
- Intermediate
Tutorial Contents
In order to house all the API code, there is a directory api which is a direct child of the server directory where all the code will be added.
Catalog
In the api directory, a sub-directory catalog will contain all the code for the catalog API.
The first covered module in the catalog directory is the catalog model to be used in the database.
catalog.model.js
In the catalog.model.js file we would add the database model for the catalog feature of the application.
Code
'use strict';
var mongoose = require('bluebird').promisifyAll(require('mongoose'));
var Schema = mongoose.Schema;
var slugs = require('mongoose-url-slugs');
var CatalogSchema = new Schema({
name: { type: String, required: true},
parent: { type: Schema.Types.ObjectId, ref: 'Catalog' },
ancestors: [{ type: Schema.Types.ObjectId, ref: 'Catalog' }],
children: [{ type: Schema.Types.ObjectId, ref: 'Catalog' }]
});
CatalogSchema.methods = {
addChild: function (child) {
var that = this;
child.parent = this._id;
child.ancestors = this.ancestors.concat([this._id]);
return this.model('Catalog').create(child).addCallback(function (child) {
that.children.push(child._id);
that.save();
});
}
}
CatalogSchema.plugin(slugs('name'));
module.exports = mongoose.model('Catalog', CatalogSchema);
Bluebird is set as a requirement alongside mongoose to help in the handling of Promise in the api.
Another dependency used in this file is the mongoose-url-slugs which allows us to generate and customize URL slugs.
The variable CatalogSchemagenerates a new object Schema which serves as the actual database model for the catalog.
name object will store the catalog name for each Catalog. It has a data type of String and the property required: true means that this document object is required in the creation of a catalog document.
parent will store the names of the catalog parent for each Catalog. It has a data type of ObjectId which was set through type: Schema.Types.ObjectId.
ancestors is an array of objects that will store the names of the catalog ancestors for each Catalog. Like parent it has a data type of ObjectId which was set through type: Schema.Types.ObjectId.
children is an array of objects that will store the names of the catalog children for each Catalog. Like parent and ancestors it has a data type of ObjectId which was set through type: Schema.Types.ObjectId.
The CatalogSchema object has a method addChild() which dictates how the catalog parent, child and ancestors relate with each other when adding a new child catalog.
catalog.controller.js
In this file the code for the catalog controller module will be added.
The contents of this file will contain code that accepts requests from the view and passes it to the database for execution and also listens for a response to be returned and rendered in the view.
Code
/**
* Using Rails-like standard naming convention for endpoints.
* GET /api/catalogs -> index
* POST /api/catalogs -> create
* GET /api/catalogs/:id -> show
* PUT /api/catalogs/:id -> update
* DELETE /api/catalogs/:id -> destroy
*/
'use strict';
var _ = require('lodash');
var Catalog = require('./catalog.model');
function handleError(res, statusCode) {
statusCode = statusCode || 500;
return function(err) {
res.status(statusCode).send(err);
};
}
function responseWithResult(res, statusCode) {
statusCode = statusCode || 200;
return function(entity) {
if (entity) {
res.status(statusCode).json(entity);
}
};
}
function handleEntityNotFound(res) {
return function(entity) {
if (!entity) {
res.status(404).end();
return null;
}
return entity;
};
}
function saveUpdates(updates) {
return function(entity) {
var updated = _.merge(entity, updates);
return updated.saveAsync()
.spread(function(updated) {
return updated;
});
};
}
function removeEntity(res) {
return function(entity) {
if (entity) {
return entity.removeAsync()
.then(function() {
res.status(204).end();
});
}
};
}
// Gets a list of Catalogs
exports.index = function(req, res) {
Catalog.find().sort({_id: 1}).execAsync()
.then(responseWithResult(res))
.catch(handleError(res));
};
// Gets a single Catalog from the DB
exports.show = function(req, res) {
Catalog.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(responseWithResult(res))
.catch(handleError(res));
};
// Creates a new Catalog in the DB
exports.create = function(req, res) {
Catalog.createAsync(req.body)
.then(responseWithResult(res, 201))
.catch(handleError(res));
};
// Updates an existing Catalog in the DB
exports.update = function(req, res) {
if (req.body._id) {
delete req.body._id;
}
Catalog.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(saveUpdates(req.body))
.then(responseWithResult(res))
.catch(handleError(res));
};
// Deletes a Catalog from the DB
exports.destroy = function(req, res) {
Catalog.findByIdAsync(req.params.id)
.then(handleEntityNotFound(res))
.then(removeEntity(res))
.catch(handleError(res));
};
The catalog controller has two two required modules which includes the installed lodash dependency and the catalog.model.js module.
The controller also has a number of functions that handles the different states of the catalogs during operations.
handleError() is called whenever executions relating to the catalog module encounters an error.
The function returns the status code of the error being encountered or a 500 status code.
responseWithResult() checks for the presence of a variable entity. If entity exists the function returns a 200 status code with a json version of entity.
handleEntityNotFound() also checks for the presence of entity. Only this time, if entity is not found the function returns a 404 status code, else the function just returns entity.
saveUpdates() accepts new values from updates and merges them with existing values in entity.
The function then proceeds to save the newly merged values to the database and returns the value of the newly merged values in updated.
removeEntity() first of all checks for the existence of entity, if it exists the function returns a method removeAsync() which removes entity from the database.
exports.index is an exported function that looks through the database and returns a list of documents in the Catalog collection.
exports.show is an exported function that looks through the database and returns the single Catalog document with an id matching the one provided in req.params.id.
exports.create is an exported function that will create a new document in the Catalog collection.
exports.update is an exported function that will update an existing document in the Catalog collection with data provided req.body.
exports.destroy is an exported function that will look for a document in the Catalog collection matching the one in req.params.id and remove it from the database.
All exported functions in this file makes use of the other local functions to handle errors, updates, deletions and creations in the Catalog database collection.
catalog.events.js
In this file we'll add/register an event emitter to the events in the Catalog model.
/**
* Catalog model events
*/
'use strict';
var EventEmitter = require('events').EventEmitter;
var Catalog = require('./catalog.model');
var CatalogEvents = new EventEmitter();
// Set max event listeners (0 == unlimited)
CatalogEvents.setMaxListeners(0);
// Model events
var events = {
'save': 'save',
'remove': 'remove'
};
// Register the event emitter to the model events
for (var e in events) {
var event = events[e];
Catalog.schema.post(e, emitEvent(event));
}
function emitEvent(event) {
return function(doc) {
CatalogEvents.emit(event + ':' + doc._id, doc);
CatalogEvents.emit(event, doc);
}
}
module.exports = CatalogEvents;
In this file the events module and Catalog model are set as requirements to be used during operation.
The line CatalogEvents.setMaxListeners(0); will set the number of event listeners that can be set up to unlimited.
The model events to listen for are stored in an object events highlighted in the code block below
var events = {
'save': 'save',
'remove': 'remove'
};
Event listeners are going to be on the lookout for any save or remove events in the Catalog.
The following code block will help register the event emitter to the model events
for (var e in events) {
var event = events[e];
Catalog.schema.post(e, emitEvent(event));
}
events is an array and for event e in the events array the block above posts e while also calling the emitEvent() function on event.
emitEvent() returns a function which emit the events stored in CatalogEvents using the emit method.
catalog.socket.js
In this module the listeners responsible for listening to events emitted in catalog.events.js are set up.
Code
/**
* Broadcast updates to client when the model changes
*/
'use strict';
var CatalogEvents = require('./catalog.events');
// Model events to emit
var events = ['save', 'remove'];
exports.register = function(socket) {
// Bind model events to socket events
for (var i = 0, eventsLength = events.length; i < eventsLength; i++) {
var event = events[i];
var listener = createListener('catalog:' + event, socket);
CatalogEvents.on(event, listener);
socket.on('disconnect', removeListener(event, listener));
}
};
function createListener(event, socket) {
return function(doc) {
socket.emit(event, doc);
};
}
function removeListener(event, listener) {
return function() {
CatalogEvents.removeListener(event, listener);
};
}
catalog.events.js is set as a requirement in this file.
The module stores a list of events to emit in the array events as shown below
var events = ['save', 'remove'];
For each event emitted in the events module we are going to create a listener that will listen and handle the event. The block of code below will handle the creation and removal of thee event listener.\
exports.register = function(socket) {
// Bind model events to socket events
for (var i = 0, eventsLength = events.length; i < eventsLength; i++) {
var event = events[i];
var listener = createListener('catalog:' + event, socket);
CatalogEvents.on(event, listener);
socket.on('disconnect', removeListener(event, listener));
}
};
For each event whose index is less than the total number of events. The createListener() function is stored in a variable listener and is passed the parameters event and socket which will create a listener for the specific event happening.
The line CatalogEvents.on(event, listener) means that whenever an event occurs the callback function in listener should run.
When socket disconnects from the server, the removeListener() function runs which removes the created event listener from the module.
index.js
In the index.js file we are going to use the express router module to get the exported functions the catalog controller so they can be used other parts of the application.
Code
'use strict';
var express = require('express');
var controller = require('./catalog.controller');
var router = express.Router();
router.get('/', controller.index);
router.get('/:id', controller.show);
router.post('/', controller.create);
router.put('/:id', controller.update);
router.patch('/:id', controller.update);
router.delete('/:id', controller.destroy);
module.exports = router;
In the code above the express module is set as a requirement. and the express router is gotten through the line var router = express.Router();.
Using inbuilt router methods, this module creates, reads, updates and deletes catalogs from the catalog controller.
The entire Catalog module is useful and vital in the creation of product categories and sub categories in the application.
In the next tutorial we will work on the order API which handles product orders in the store.
Thank you for your contribution.
As I understood from your tutorial, it is fully based on a book that you referenced by Adrian Mejia. Similarly is the case for the code.
We would really look forward to something more innovative, and not merely an explanation or revisit of pre-explained and developed code available elsewhere, particularly as is.
We hope you can come up with more innovative and informative tutorials, which we would be awaiting in anticipation.
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]
Thank you for your review, @mcfarhat! Keep up the good work!
Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!

Reply !stop to disable the comment. Thanks!
You have been pretty much consistent here, your posts are great CV that shows your capability. Well done!
Hi, @gotgame!
You just got a 0.62% upvote from SteemPlus!
To get higher upvotes, earn more SteemPlus Points (SPP). On your Steemit wallet, check your SPP balance and click on "How to earn SPP?" to find out all the ways to earn.
If you're not using SteemPlus yet, please check our last posts in here to see the many ways in which SteemPlus can improve your Steem experience on Steemit and Busy.
Congratulations @gotgame! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :
Click here to view your Board of Honor
If you no longer want to receive notifications, reply to this comment with the word
STOPDo not miss the last post from @steemitboard:
Congratulations @gotgame! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :
Click here to view your Board of Honor
If you no longer want to receive notifications, reply to this comment with the word
STOP