Tutorial 4: Building An Ecommerce Platform With The MEAN Stack
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 CatalogSchema
generates 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
STOP
Do 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