How to Build a Scalable eCommerce Platform with MEAN Stack

In the modern digital era, the eCommerce industry has grown exponentially, and online shopping has become an integral part of our daily lives. With the increasing demand for online shopping, businesses need to build scalable eCommerce platforms that can handle large volumes of traffic, transactions, and users. This is where the MEAN stack comes into play.  

MEAN stack is a collection of JavaScript-based technologies that are widely used for web development, including MongoDB, Express, Angular, and Node.js. MEAN stack provides an efficient, flexible, and scalable platform for building robust web applications.  

In this blog, we will guide you through the process of building a scalable eCommerce platform using the MEAN stack. We will cover everything from planning and setting up the environment to developing the front end and back end, as well as optimizing the platform for scalability.   

By the end of this blog, you will have a solid understanding of how to build a scalable eCommerce platform using the MEAN stack and be equipped with the tools you need to build your eCommerce platform. So, let’s get started! 

Part 1: Planning the eCommerce Platform: 

Before you start building an eCommerce platform with MEAN stack, it’s essential to plan the project carefully. This involves conducting market research, analyzing competitors, defining the project scope, and identifying key features. Let’s explore these steps in detail: 

Conducting market research and analyzing competitors:  

To build a successful eCommerce platform, you need to understand your target audience and their needs. Conducting market research will help you identify the trends, preferences, and pain points of your customers. You can use tools like Google Analytics, social media analytics, and surveys to gather this information.  

Analyzing your competitors can also provide valuable insights into the market. You can study their products, pricing, user experience, and marketing strategies to identify their strengths and weaknesses. This information can help you differentiate your platform and provide a better user experience to your customers. 

Defining the project scope and identifying key features:  

Based on market research and competitor analysis, you can define the project scope and identify the key features of your eCommerce platform. This involves creating a product roadmap and prioritizing the features based on their importance and feasibility. 

Some of the essential features of an eCommerce platform include a product catalog, shopping cart, payment gateway, order management, customer management, and analytics. You can also include advanced features like personalized recommendations, social sharing, and loyalty programs, depending on your business needs. 

Creating wireframes and user stories:  

Once you have defined the project scope and identified the key features, you can create wireframes and user stories to visualize the platform’s user interface and user experience. Wireframes are low-fidelity visual representations of the platform’s layout, navigation, and functionality. User stories are descriptions of the user’s goals and tasks on the platform.  

You can use tools like Balsamiq, Sketch, or Figma to create wireframes and user stories. These tools allow you to collaborate with your team and stakeholders and get feedback on the design and functionality of the platform.  

By following these steps, you can plan the eCommerce platform effectively and ensure that it meets the needs of your customers and business. In the next section, we’ll explore the steps involved in setting up the MEAN stack environment. 

Part 2: Setting up the MEAN Stack Environment:

Now that you have planned the eCommerce platform, it’s time to set up the MEAN stack environment. MEAN stack comprises four technologies – MongoDB, Express, Angular, and Node.js. Let’s explore the steps involved in setting up each of these technologies:  

Installing Node.js and MongoDB: 

The first step is to install Node.js and MongoDB on your computer. Node.js is a JavaScript runtime that allows you to run JavaScript on the server side, while MongoDB is a NoSQL database that stores data in JSON-like documents. You can download and install Node.js and MongoDB from their respective websites. 

Setting up the Express framework: 

Express is a popular Node.js framework for building web applications. To set up Express, you can create a new Node.js project and install the Express module using npm, the Node.js package manager. You can use the following commands in the terminal: 

mkdir myapp 
cd myapp 
npm init 
npm install express

This will create a new directory called myapp, initialize a new Node.js project with a package.json file, and install the Express module. 

Configuring the Angular frontend framework:  

Angular is a popular frontend framework for building dynamic single-page applications. To configure Angular, you can create a new Angular project using the Angular CLI, which is a command-line interface for creating and managing Angular projects. You can use the following commands in the terminal: 

npm install -g @angular/cli 
ng new myapp

This will install the Angular CLI globally on your computer and create a new Angular project called myapp. 

Testing the setup:  

Once you have set up the environment, you can test it by running the Node.js server and opening the Angular app in your browser. To run the Node.js server, you can create a new file called server.js in the root directory of your project and add the following code: 

const express = require('express');
const app = express();
const port = 3000;  

app.get('/', (req, res) => {
  res.send('Hello World!');
});  

app.listen(port, () => {
     console.log(`Server listening on port ${port}`);
});

This will create a new Express app with a route that sends a “Hello World!” message when you visit the root URL. To start the server, you can run the following command in the terminal: 

node server.js

This will start the Node.js server and you should see the message “Server listening on port 3000” in the terminal. Next, you can open the Angular app by running the following commands in a new terminal window: 

cd myapp 
ng serve

This will start the Angular development server and you can open the app in your browser by visiting http://localhost:4200. If everything is set up correctly, you should see the default Angular app with the message “Welcome to myapp!”.  

These are the basic steps for setting up the MEAN stack environment. Of course, there are many additional configurations and dependencies you can add depending on your project requirements. 

Part 3: Building the Backend:

In this section, we will focus on building the backend of the eCommerce platform using the MEAN stack. 

1. Creating a RESTful API using Express:  

RESTful APIs are the backbone of modern web applications. They allow client-side applications to communicate with server-side applications through a uniform interface. To create a RESTful API using Express, follow these steps: 

  • Install the required dependencies by running the following command: 

npm install express mongoose body-parser cors 

  • Create a new file called “api.js” in the Express project folder and add the following code: 
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cors = require('cors');
const passport = require('passport');

const app = express();

const port = process.env.PORT || 3000;
const mongoUrl = process.env.MONGO_URL || 'mongodb://localhost:27017/myapp';

mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('Connected to MongoDB'))
  .catch(err => console.log(err));

app.use(bodyParser.json());
app.use(cors());
app.use(passport.initialize());

app.get('/', (req, res) => {
  res.send('Hello, world!');
});

app.listen(port, () => {
  console.log(`Server started on port ${port}`);
});

  • Test the API by running the command “node api.js” and opening a web browser at “http://localhost:3000“. You should see the message “Hello, world!” displayed.

2. Integrating MongoDB for data storage and retrieval: 

MongoDB is a NoSQL database that is often used in MEAN stack applications. To integrate MongoDB with our Express API, follow these steps:  

  • Install the MongoDB driver for Node.js by running the following command: 

npm install mongodb 

  • Create a connection to MongoDB in the “app.js” file by adding the following code: 
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/ecommerce', { useNewUrlParser: true })
  .then(() => console.log('MongoDB conne
cted'))
  .catch(err => console.log(err));

This code creates a connection to a local MongoDB instance running on port 27017, and sets up a database named “ecommerce”. If the connection is successful, a message is logged to the console. 

  • Create a Mongoose model for our products in a new file named “product.js” in the “models” directory: 
const mongoose = require('mongoose'); 

const productSchema = new mongoose.Schema({ 
  name: { type: String, required: true }, 
  description: { type: String }, 
  price: { type: Number, required: true }, 
  image: { type: String } 
}); 

module.exports = mongoose.model('Product', productSchema);

This code defines a Mongoose schema for our products, which includes fields for the product name, description, price, and image. The schema is then exported as a Mongoose model named “Product”. 

  • Update the “api.js” file to use our Mongoose model for products: 
const express = require('express'); 
const router = express.Router(); 
const Product = require('../models/product'); 

// Create a new product 
router.post('/products', (req, res) => { 
  const newProduct = new Product(req.body);
  newProduct.save() 
    .then(product => res.json(product)) 
    .catch(err => res.status(500).json({ error: err })); 
}); 

// Get all products 
router.get('/products', (req, res) => { 
  Product.find() 
    .then(products => res.json(products)) 
    .catch(err => res.status(500).json({ error: err })); 
}); 

// Get a single product by id 
router.get('/products/:id', (req, res) => { 
  Product.findById(req.params.id) 
    .then(product => res.json(product)) 
    .catch(err => res.status(500).json({ error: err })); 
}); 

// Update a product by id 
router.put('/products/:id', (req, res) => { 
  Product.findByIdAndUpdate(req.params.id, req.body) 
    .then(() => res.status(204).send()) 
    .catch(err => res.status(500).json({ error: err })); 
}); 

// Delete a product by id 
router.delete('/products/:id', (req, res) => { 
  Product.findByIdAndDelete(req.params.id) 
    .then(() => res.status(204).send()) 
    .catch(err => res.status(500).json({ error: err })); 
}); 

module.exports = router;  

With the above code, our Express endpoints for creating, reading, updating, and deleting products now use the Mongoose model to interact with the MongoDB database. 

By following these steps, we have successfully integrated MongoDB for data storage and retrieval in our MEAN stack eCommerce platform.  

3. Implementing user authentication and authorization:

 Implementing user authentication and authorization is an important part of building any web application that requires users to have accounts and access to specific resources. In this section, we will discuss how to implement user authentication and authorization using JSON Web Tokens (JWT) in our Express API. 

  • Install necessary packages First, we need to install the required packages for user authentication and authorization. We can use the jsonwebtoken package for generating and verifying JWTs, and the bcryptjs package for hashing passwords.

npm install jsonwebtoken bcryptjs 

  • Create a User Model  

Next, we need to create a User model to store user information in our MongoDB database. We can create a User.js file in a new models folder and define the schema for our User model. 

const mongoose = require('mongoose'); 
const Schema = mongoose.Schema; 

const UserSchema = new Schema({ 
  name: { 
    type: String, 
    required: true 
  }, 
  email: { 
    type: String, 
    required: true, 
    unique: true 
  }, 
  password: { 
    type: String, 
    required: true 
  }, 
  isAdmin: { 
    type: Boolean, 
    default: false 
  } 
}); 

module.exports = User = mongoose.model('user', UserSchema);

Here, we define the fields name, email, password, and isAdmin for our User model. The email field is marked as unique so that we cannot have multiple users with the same email address. 

  • Create a user registration route  

We can create a new route in our Express API to handle user registration. In this route, we will take in the user’s name, email, and password, hash the password using bcryptjs, and then save the user to our database. 

const express = require('express'); 
const router = express.Router(); 
const bcrypt = require('bcryptjs'); 
const jwt = require('jsonwebtoken');
const { check, validationResult } = require('express-validator'); 
const User = require('../models/User'); 

// @route POST api/users 
// @desc Register user 
// @access Public 
router.post( 
  '/', 
  [ 
    check('name', 'Please add name').not().isEmpty(), 
    check('email', 'Please include a valid email').isEmail(), 
    check( 
      'password', 
      'Please enter a password with 6 or more characters' 
    ).isLength({ min: 6 }), 
  ], 

 async (req, res) => { 
    const errors = validationResult(req); 
    if (!errors.isEmpty()) { 
      return res.status(400).json({ errors: errors.array() }); 
    } 

    const { name, email, password } = req.body; 

    try { 
      let user = await User.findOne({ email }); 

      if (user) { 
        return res.status(400).json({ msg: 'User already exists' }); 
      } 

      user = new User({ 
        name, 
        email,
        password, 
      }); 

      const salt = await bcrypt.genSalt(10); 

      user.password = await bcrypt.hash(password, salt); 

      await user.save(); 

      const payload = { 
        user: { 
          id: user.id, 
        }, 
      }; 

      jwt.sign( 
        payload,
        process.env.JWT_SECRET, 
        { expiresIn: 360000 }, 
        (err, token) => { 
          if (err) throw err; 
          res.json({ token });
        } 
      ); 
    } catch (err) { 
      console.error(err.message); 
      res.status(500).send('Server Error'); 
    } 
  } 
);
module.exports = router; 

After creating the user registration route, the next step is to implement the user login functionality. This involves creating a login route and verifying the user’s credentials against the stored data in MongoDB.  

  • Create user login route:  

Similar to the user registration route, create a new route for user login in the auth.js file using the express Router method. 

router.post('/login', async (req, res) => { 
  // Implement login functionality here 
}); 

  • Verify user credentials:  

In the login route, retrieve the user’s email and password from the request body. Then, verify the credentials against the user data stored in the MongoDB database. 

router.post('/login', async (req, res) => { 
  const { email, password } = req.body; 

  // Find user by email 
  const user = await User.findOne({ email }); 

  // Verify user password 
  if (!user || !bcrypt.compareSync(password, user.password)) { 
    return res.status(401).json({ error: 'Invalid email or password' }); 
  } 

  // Create and assign JWT token 
  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET); 
  res.json({ token }); 

}); 

Here, we are using the bcrypt.compareSync() method to compare the provided password with the hashed password stored in the database. If the user does not exist or the password is incorrect, return a 401 unauthorized error. If the credentials are valid, create a JWT token using the jsonwebtoken library and send it back to the client.  

  • Protect routes with authorization:  

To protect routes that require authorization, create a middleware function that verifies the presence and validity of the JWT token in the request header. 

const verifyToken = (req, res, next) => { 
  const token = req.headers.authorization; 

  if (!token) { 
    return res.status(401).json({ error: 'Unauthorized request' }); 
  } 

  try { 
    const decoded = jwt.verify(token, process.env.JWT_SECRET); 
    req.userId = decoded.id;
 next(); 
  } catch (err) { 
    return res.status(401).json({ error: 'Invalid token' }); 
  } 
}; 

Here, we are extracting the JWT token from the Authorization header and verifying its validity using the jsonwebtoken library. If the token is valid, we attach the decoded user ID to the request object and call the next() function to continue with the next middleware or route handler. Otherwise, return a 401 unauthorized error.  

  • Protect routes with authorization middleware:  

To protect a route with the authorization middleware, add the verifyToken function as middleware to the route. This middleware function will check if the user is authenticated and has a valid token before allowing access to the route. Here’s an example: 

router.get('/profile', verifyToken, (req, res) => { 
  // code to retrieve user profile 
}); 

In this example, the verifyToken function is added as middleware to the /profile route. This means that the function will be called before the route handler is executed. If the function determines that the user is not authenticated or does not have a valid token, it will return an error response and the route handler will not be executed.  

Here’s an example implementation of the verifyToken function: 

const jwt = require('jsonwebtoken'); 

function verifyToken(req, res, next) { 
  const token = req.headers['authorization']; 

  if (!token) { 
    return res.status(401).json({ message: 'Unauthorized access' }); 
  } 

  try { 
    const decoded = jwt.verify(token, process.env.JWT_SECRET); 
    req.user = decoded.user; 
    next(); 
  } catch (err) { 
    return res.status(401).json({ message: 'Invalid token' }); 
  } 
}

This function first retrieves the token from the Authorization header of the request. If the token is not present, it returns an error response. If the token is present, it attempts to verify the token using the JWT library and the secret key defined in the environment variables. If the token is valid, it extracts the user information from the token and adds it to the request object. Finally, it calls the next function to pass control to the next middleware function or the route handler. 

By using this authorization middleware, you can protect routes that require authentication and ensure that only authenticated users can access them. 

Once the token has been verified, the middleware function calls next() to move on to the next function in the request chain. 

With the authorization middleware in place, any routes that need to be protected can be wrapped in the middleware function by simply including it as a parameter in the route definition, like this: 

router.get('/protected', verifyToken, (req, res) => { 
  // Handle protected route 
}); 

This will ensure that only authenticated users with a valid JWT can access the protected route. 

4. Handling payments with Stripe API:

After completing the backend functionality, it’s time to integrate a payment gateway to enable customers to make payments securely. Stripe is a popular payment gateway that provides APIs for integrating payments into web and mobile applications. 

Here’s how to handle payments with Stripe API: 

  • First, sign up for a Stripe account at https://dashboard.stripe.com/register. You will get an API key that you will use to authenticate requests to the Stripe API. 
  • Install the Stripe Node.js library by running the following command in the terminal: 

npm install stripe 

  • In the routes directory, create a new file named payment.js. This file will contain the code for handling payments with the Stripe API. 
  • In payment.js, require the Stripe Node.js library and create a new instance of the stripe object, passing your Stripe API key as the argument: 
const stripe = require('stripe')('your_stripe_api_key');
  • Create a new route for processing payments by adding the following code to payment.js: 
router.post('/charge', async (req, res) => { 
  try { 
    const amount = req.body.amount; 
    const paymentIntent = await stripe.paymentIntents.create({ 
      amount, 
      currency: 'usd', 
    }); 
    res.json({ client_secret: paymentIntent.client_secret }); 
  } catch (err) { 
    res.status(500).json({ message: err.message }); 
  } 
});
  • The charge route accepts a POST request containing the amount to be charged in cents. It creates a new payment intent with Stripe, which represents the payment and contains information about the customer, payment method, and transaction. The client_secret property of the payment intent is returned to the client as a response to the request. 
  • In the Angular frontend, create a new service named payment.service.ts. This service will handle communication with the payment API endpoint. 
  • In payment.service.ts, import the HttpClient module and inject it into the constructor: 
import { HttpClient } from '@angular/common/http'; 

@Injectable({ 
  providedIn: 'root' 
}) 

export class PaymentService { 
  constructor(private http: HttpClient) { } 
  charge(amount: number) { 
    return this.http.post('/api/payment/charge', { amount }); 
  } 
}
  • The charge method sends a POST request to the /api/payment/charge endpoint with the amount to be charged as the payload. 
  • In the checkout component, import the PaymentService and inject it into the constructor: 
import { PaymentService } from '../payment.service'; 
constructor(private paymentService: PaymentService) { }
  • Add the following code to the checkout component to create a new payment intent and process the payment: 
async processPayment() { 
  const amount = this.cart.totalPrice * 100; 
  const { client_secret } = await this.paymentService.charge(amount).toPromise(); 
  const { error, paymentIntent } = await stripe.confirmCardPayment(client_secret, { 
    payment_method: { 
      card: this.cardElement, 
      billing_details: { 
        name: this.name, 
        email: this.email
      } 
    } 
  }); 
  if (error) { 
    console.log(error.message); 
  } else { 
    console.log('Payment successful'); 
  } 
}
  • The processPayment method calculates the amount to be charged by multiplying the total price of the items in the cart by 100, since Stripe requires the amount to be specified in cents. It then creates a new Stripe charge object with the amount, currency, and other details, using the Stripe API key. If the charge is successful, it creates a new order object in the database with the user ID, items, total price, and other details, and returns a success message. 

know more about our services

Part 4: Developing the Frontend: 

Designing the user interface with Angular: 

Angular is a popular JavaScript framework for building dynamic single-page applications (SPAs). In this part of the project, we will be using Angular to design and implement the user interface for our eCommerce website. We will create a set of components that will be responsible for different parts of the website, such as the header, footer, product list, and shopping cart. 

To make our eCommerce website more user-friendly, we will add several features such as search, filter, and cart management. The search feature will allow users to search for products based on keywords or product names. The filter feature will allow users to filter products based on various criteria, such as price range, product category, or brand. The cart management feature will allow users to add products to their shopping cart, view their cart contents, and modify or remove items from their cart. 

Optimizing the front end for performance and user experience: 

To ensure a smooth and seamless user experience, we will optimize the front end for performance. We will implement lazy loading of components, which means that components will only be loaded when they are actually needed, instead of loading all components at once. We will also implement caching of frequently accessed data, such as product information, to reduce server load and improve response times. Additionally, we will optimize the website for mobile devices to ensure that it is accessible to users on different devices and screen sizes. 

Part 5: Scaling the Platform 

Scaling a web application is essential to handle increased traffic and user load. In this section, we will discuss some of the steps that can be taken to scale the platform.  

Implementing caching with Redis:  

Caching is an essential component for improving the performance of web applications. Redis is an open-source, in-memory data structure store that can be used as a database, cache, and message broker. We can use Redis to cache frequently accessed data and speed up the response time of our application. 

To implement caching with Redis, we first need to install Redis and add the Redis client library to our Node.js application. We can use the “ioredis” library, which provides a simple and powerful Redis client for Node.js. 

Once we have installed the Redis client library, we can create a Redis instance in our Node.js application and use it to cache data. For example, we can cache the results of frequently accessed database queries or API requests. 

Setting up load balancing with Nginx:  

Load balancing is a technique used to distribute incoming network traffic across multiple servers to improve the performance, reliability, and scalability of web applications. Nginx is a popular open-source web server that can be used as a load balancer. 

To set up load balancing with Nginx, we first need to install Nginx on our server and configure it as a load balancer. We can define a set of upstream servers, which can be multiple instances of our Node.js application, and configure Nginx to distribute incoming traffic among them. We can use different load balancing algorithms, such as round-robin or least connections, to distribute the traffic. 

Deploying the application to a cloud server:  

Cloud computing has revolutionized the way applications are deployed and managed. Cloud providers, such as AWS, Google Cloud, and Microsoft Azure, offer a range of services for deploying and scaling web applications. 

To deploy our Node.js application to a cloud server, we first need to choose a cloud provider and create a virtual machine instance. We can then install Node.js, Nginx, Redis, and any other dependencies on the server. We can use a process manager, such as PM2, to manage our Node.js application and keep it running in the background. 

Monitoring and optimizing the platform for scalability:  

Scalability is a critical factor in the success of web applications. As the traffic to our application grows, we need to ensure that our platform can handle the increased load. We can use various tools and techniques to monitor and optimize our platform for scalability. 

We can use monitoring tools, such as New Relic or Datadog, to monitor the performance and availability of our application and server. We can use load-testing tools, such as Apache JMeter or Artillery, to simulate high traffic and test the scalability of our platform. We can use performance optimization techniques, such as caching, database optimization, and code optimization, to improve the performance of our application. We can also use autoscaling techniques, such as horizontal scaling or vertical scaling, to dynamically adjust the capacity of our platform based on the traffic load.  

Wrapping it Up: 

Building an e-commerce platform can be a complex and challenging process, but by following the right steps and using the right tools and technologies, it is achievable. We have seen the important steps involved in building an e-commerce platform from scratch, including setting up the environment, building the backend, developing the front end, and scaling the platform.  

At Imenso Software, we have a team of experienced developers who can help you build a custom e-commerce platform tailored to your business needs. Contact us today to get started on your e-commerce journey. 

Similar Posts
September 10, 2023 | 11 min read
AngularJS vs ReactJS vs VueJS: Which is Best for Your Business? 

Are you facing the daunting task of choosing the perfect front-end technology for your business? Wondering which framework will best suit your needs and propel your projects toward success? Look no further! In this comprehensive blog, we aim to answer the burning question on every developer’s mind: AngularJS vs Top 3 Frontend Technologies: Which is […]...

CSS Frameworks 2018
August 10, 2018 | 13 min read
Freewheeling with Design- Five Most Popular CSS Frameworks of 2018 for You

‘What does the customer want?’ As a developer, you wonder about, worry over, and meditate around it numerous times a day. After all, adapting any solution to a user’s need is as much a part of your job as is to filter through their requirements and pinpoint their needs. CSS frameworks support you in such […]...

Top 10 Javascript Frameworks
July 25, 2018 | 9 min read
Top 10 Javascript Frameworks

There are several popular Javascript frameworks out there and there is continuous effort to add more values to the code repository. But with such expanding range of choices, often it is a little confusing to decide on a framework that you actually need. Well, this is when a detailed comparison of the most popular and […]...

Learn more about software outsourcing
#imenso

Think Big

Rated 4.7 out of 5 based on 34 Google reviews.