Tutorial 2: Building An Ecommerce Platform With The MEAN Stack
Repository
https://github.com/nodejs/node
What Will I Learn
This is the second 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 showed you how the backend works and did a general introduction to the series.
In this tutorial we would build our application a step further by working on the configurations relating to the database and some special modules/dependencies.
By the end of this tutorial we would have set up the application config
directory and some files required with the folder.
Requirements
- NodeJS and NPM,
- Angular
- MongoDB
- Code Editor
Difficulty
- Intermediate
Tutorial Contents
In order to setup the other configurations for the application database and backend I created the folder config
as the direct child of the server
directory.
The config
directory was somewhat mentioned in the first tutorial as part of the requirements needed in the app.js
file.
In the config
directory there is a number of files and one sub directory performing certain special functions.
express.js
The first configuration file in the config
directory. The code contents of this file is responsible for connecting some backend dependencies with the database where they are required
In the express.js
file we have the following code
/**
* Express configuration
*/
'use strict';
var express = require('express');
var favicon = require('serve-favicon');
var morgan = require('morgan');
var compression = require('compression');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var cookieParser = require('cookie-parser');
var errorHandler = require('errorhandler');
var path = require('path');
var config = require('./environment');
var passport = require('passport');
var session = require('express-session');
var mongoStore = require('connect-mongo')(session);
var mongoose = require('mongoose');
module.exports = function(app) {
var env = app.get('env');
app.set('views', config.root + '/server/views');
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(compression());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(methodOverride());
app.use(cookieParser());
app.use(passport.initialize());
// Persist sessions with mongoStore / sequelizeStore
// We need to enable sessions for passport twitter because its an oauth 1.0 strategy
app.use(session({
secret: config.secrets.session,
resave: true,
saveUninitialized: true,
store: new mongoStore({
mongooseConnection: mongoose.connection,
db: 'meanshop'
})
}));
app.set('appPath', path.join(config.root, 'client'));
if ('production' === env) {
app.use(favicon(path.join(config.root, 'client', 'favicon.ico')));
app.use(express.static(app.get('appPath')));
app.use(morgan('dev'));
}
if ('development' === env || 'test' === env) {
app.use(require('connect-livereload')());
app.use(express.static(path.join(config.root, '.tmp')));
app.use(express.static(app.get('appPath')));
if('test' !== env) app.use(morgan('dev'));
app.use(errorHandler()); // Error handler - has to be last
}
};
The require hook was utilized in this file to set all needed modules up for use.
After setting up the file dependencies a function is exported through module.exports
. This function contains all the actual configurations for this file.
There are three environments in the application including development
, production
and test
.
This line of code var env = app.get('env');
will help determine the present environment the application is running on.
The following block also sets further configurations relating to the front end, data parsing and some
app.set('views', config.root + '/server/views');
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(compression());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(methodOverride());
app.use(cookieParser());
app.use(passport.initialize());
In the block above, the path to the views
folder which will contain the html
file to the Not Found Error
feature is set to '/server/views'
through the line app.set('views', config.root + '/server/views');
.
app.engine('html', require('ejs').renderFile);
is responsible for setting the html
templating engine module ejs
as a requirement.
The view engine is then set as ejs
through the lineapp.set('view engine', 'html');
app.use(compression());
will use the compression
package in order to enable gzip compression
in the application.
Other module set for use in the block include bodyparser
, method override
, cookie parser
and passport
.
The block below will help to enable sessions for passport twitter.
app.use(session({
secret: config.secrets.session,
resave: true,
saveUninitialized: true,
store: new mongoStore({
mongooseConnection: mongoose.connection,
db: 'meanshop'
})
}));
The block above uses the express-session
and connect-mongo
module to create a database connection to the MongoDB
database
If the application is running on the production environment the following block executes
if ('production' === env) {
app.use(favicon(path.join(config.root, 'client', 'favicon.ico')));
app.use(express.static(app.get('appPath')));
app.use(morgan('dev'));
}
In the code above the path to the favicon
is firstly set using the path
module.
The static front end folder files is also set to the value of the appPath
directive which was set earlier in the file.
In case the application is running on the development
or the test
environment the following configuration is set.
if ('development' === env || 'test' === env) {
app.use(require('connect-livereload')());
app.use(express.static(path.join(config.root, '.tmp')));
app.use(express.static(app.get('appPath')));
if('test' !== env) app.use(morgan('dev'));
app.use(errorHandler()); // Error handler - has to be last
}
On the first line connect-liverreload
is used as the middleware for adding the liverreload
script.
Second and third line sets the front end folder using the express.static()
method to to set the directory path.
locals.env.js
In the locals.env.js
file, the admin/dev of the store can store the api
keys to be used for authentication from different services.
The code
'use strict';
// Use local.env.js for environment variables that grunt will set when the server starts locally.
// Use for your api keys, secrets, etc. This file should not be tracked by git.
//
// You will need to set these on the server you deploy to.
module.exports = {
DOMAIN: 'http://localhost:9000',
SESSION_SECRET: 'meanshop-secret',
FACEBOOK_ID: 'app-id',
FACEBOOK_SECRET: 'secret',
TWITTER_ID: 'app-id',
TWITTER_SECRET: 'secret',
GOOGLE_ID: 'app-id',
GOOGLE_SECRET: 'secret',
BRAINTREE_ID: 'public key',
BRAINTREE_SECRET: 'private key',
BRAINTREE_MERCHANT: 'merchant ID',
// Control debug level for modules using visionmedia/debug
DEBUG: ''
};
Each key value pair represents the data needed from each service provider in order to be able use their service for authentication.
The services in the file includes Facebook authentication
, Twitter Authentication
, Google Authentication
and Braintree Authentication
seed.js
The seed.js
file is responsible for helping to populate the database with sample data that will be used for application testing purposes.
Among the sample data included in the file we can find
sample user data to enable the admin login to the store as admin and perform admin related operations
sample products with different categories for populating the store
Code
/**
* Populate DB with sample data on server start
* to disable, edit config/environment/index.js, and set `seedDB: false`
*/
'use strict';
var User = require('../api/user/user.model');
var Product = require('../api/product/product.model');
var Catalog = require('../api/catalog/catalog.model');
var mainCatalog, home, books, clothing;
User.find({}).removeAsync()
.then(function() {
User.createAsync({
provider: 'local',
name: 'Test User',
email: '[email protected]',
password: 'test'
}, {
provider: 'local',
role: 'admin',
name: 'Admin',
email: '[email protected]',
password: process.env.ADMIN_PASSWORD || 'admin'
})
.then(function() {
console.log('finished populating users');
});
});
Catalog
.find({})
.remove()
.then(function () {
return Catalog.create({ name: 'All'});
})
.then(function (catalog) {
mainCatalog = catalog;
return mainCatalog.addChild({name: 'Home'});
})
.then(function (category) {
home = category._id;
return mainCatalog.addChild({name: 'Books'});
})
.then(function (category) {
books = category._id;
return mainCatalog.addChild({name: 'Clothing'});
})
.then(function (category) {
clothing = category._id;
return Product.find({}).remove({});
})
.then(function() {
return Product.create({
title: 'MEAN eCommerce Book',
imageUrl: '/assets/uploads/meanbook.jpg',
price: 25,
stock: 250,
categories: [books],
description: 'Build a powerful e-commerce application quickly with MEAN, a leading full-JavaScript stack. It takes you step-by-step from creating a real-world store to managing details such as authentication, shopping carts, payment, scalability and more.'
}, {
title: 'T-shirt',
imageUrl: '/assets/uploads/meantshirt.jpg',
price: 15,
stock: 100,
categories: [clothing],
description: 'T-shirt with the MEAN stack logo'
}, {
title: 'Coffee Mug',
imageUrl: '/assets/uploads/meanmug.jpg',
price: 8,
stock: 50,
categories: [home],
description: 'Convert coffee into MEAN code'
});
})
.then(function () {
console.log('Finished populating Products with categories');
})
.then(null, function (err) {
console.error('Error populating Products & categories: ', err);
});
If you do not wish to use the sample data located in the seed.js
file just edit the config/environment/index.js
file and set seedDB: false
.
Each sample database collection to be created needs database model to be based on.
The code below sets the User model
, Product model
and Catalog model
as requirements in the file since the sample to be populated cut across users and products
var User = require('../api/user/user.model');
var Product = require('../api/product/product.model');
var Catalog = require('../api/catalog/catalog.model');
User.find({}).removeAsync()
looks for the User
collection in the database, if User
does not exist the function creates a new User
document.
After creating the user collection then the function goes on to populate it with the sample user data provided below
User.createAsync({
provider: 'local',
name: 'Test User',
email: '[email protected]',
password: 'test'
}, {
provider: 'local',
role: 'admin',
name: 'Admin',
email: '[email protected]',
password: process.env.ADMIN_PASSWORD || 'admin'
})
.then(function() {
console.log('finished populating users');
});
Each created user has a role
, name
, email
and password
field attached to their documents.
We'll cover the user model
indepth in the future.
The catalog and products data are intertwined, the following code will create a catalog collection and add product categories to the catalog
Catalog
.find({})
.remove()
.then(function () {
return Catalog.create({ name: 'All'});
})
.then(function (catalog) {
mainCatalog = catalog;
return mainCatalog.addChild({name: 'Home'});
})
.then(function (category) {
home = category._id;
return mainCatalog.addChild({name: 'Books'});
})
.then(function (category) {
books = category._id;
return mainCatalog.addChild({name: 'Clothing'});
})
.then(function (category) {
clothing = category._id;
return Product.find({}).remove({});
})
In the block above two main categories are created which are Books
and Clothing
, in addition to the over all Home
category which features all products.
After product categories are created, then the sample products are added through the block below
.then(function() {
return Product.create({
title: 'MEAN eCommerce Book',
imageUrl: '/assets/uploads/meanbook.jpg',
price: 25,
stock: 250,
categories: [books],
description: 'Build a powerful e-commerce application quickly with MEAN, a leading full-JavaScript stack. It takes you step-by-step from creating a real-world store to managing details such as authentication, shopping carts, payment, scalability and more.'
}, {
title: 'T-shirt',
imageUrl: '/assets/uploads/meantshirt.jpg',
price: 15,
stock: 100,
categories: [clothing],
description: 'T-shirt with the MEAN stack logo'
}, {
title: 'Coffee Mug',
imageUrl: '/assets/uploads/meanmug.jpg',
price: 8,
stock: 50,
categories: [home],
description: 'Convert coffee into MEAN code'
});
})
.then(function () {
console.log('Finished populating Products with categories');
})
.then(null, function (err) {
console.error('Error populating Products & categories: ', err);
});
Each created product document possesses the following properties
- Product title
- Product Image
- Product Price
- Product Stock
- Product categories
- Product description
If at the time of running the application the product is added successfully a message is logged to the console indicating the success of the additions
In the next tutorial I will complete the config
aspect of the backend which includes the environment configurations for the different environments the application will run on at different periods.
Thank you for your contribution @gotgame.
After analyzing your tutorial we suggest the following points below:
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
To view those questions and the relevant answers related to your post, click here.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]
Thank you for your review, @portugalcoin! 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!
Hi @gotgame!
Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server
Hi, @gotgame!
You just got a 0.68% 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.
Hey, @gotgame!
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!
Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!