Tutorial 4: Building An Ecommerce Platform With The MEAN Stack
utopian-io·@gotgame·
0.000 HBDTutorial 4: Building An Ecommerce Platform With The MEAN Stack
### Repository [https://github.com/nodejs/node](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](https://adrianmejia.com/) 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](https://nodejs.org/en/download/package-manager/), - [Angular](https://angular.io/) - [MongoDB](https://www.mongodb.com/) - 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. ### Curriculum 1. [Building An Ecommerce Platform With The MEAN Stack - 1](https://steemit.com/utopian-io/@gotgame/tutorial-1-building-an-ecommerce-application-with-the-mean-stack) 2. [Building An Ecommerce Platform With The MEAN Stack - 2](https://steemit.com/utopian-io/@gotgame/tutorial-2-building-an-ecommerce-platform-with-the-mean-stack) 3. [Building An Ecommerce Platform With The MEAN Stack - 3](https://steemit.com/utopian-io/@gotgame/tutorial-3-building-an-ecommerce-platform-with-the-mean-stack) ### Proof Of Work Done https://github.com/olatundeee/meanshop
👍 iamowomizz, iretiolajohnson, rashyem, ilovecoding, eforucom, angelinafx, merlin7, autofreak, steeming-hot, ctime, rabonee, nice2us, majordon, julietj, ttay, goldsmile, kaybee1, strongminded, tenacious, dival, frackunreicott, rioplotunook, geblarire, curbot, espoem, nfc, accelerator, onepercentbetter, merlion, amosbastian, effofex, codingdefined, steemchoose, suonghuynh, hakancelik, mcfarhat, jaff8, midun, moby-dick, sensation, sudefteri, teamcr, greenorange, gotgame, scipio, steem-plus,