Tutorial 4: Building An Ecommerce Platform With The MEAN Stack

in #utopian-io5 years ago

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

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.

Curriculum

  1. Building An Ecommerce Platform With The MEAN Stack - 1

  2. Building An Ecommerce Platform With The MEAN Stack - 2

  3. Building An Ecommerce Platform With The MEAN Stack - 3

Proof Of Work Done

https://github.com/olatundeee/meanshop

Sort:  

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) :

You made more than 29000 upvotes. Your next target is to reach 30000 upvotes.

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:

The Meet the Steemians Contest is over - Results are coming soon ...

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @gotgame! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

You made more than 30000 upvotes. Your next target is to reach 31000 upvotes.

Click here to view your Board of Honor
If you no longer want to receive notifications, reply to this comment with the word STOP

Support SteemitBoard's project! Vote for its witness and get one more award!

Coin Marketplace

STEEM 0.25
TRX 0.11
JST 0.032
BTC 62432.37
ETH 3003.22
USDT 1.00
SBD 3.78