Creating our product details page

Next, we will build our product details page. We’ll try this by writing out the factory service that will return the data for the selected product. Within the AWSservice provider, create the following function:

getProductDetails: function(id) {
    var d = $q.defer();

    var params = {
        'Key': {'product_id': {'S': id}
        }
    };
    dynamo.getItem(params, function(err, data) {
        if (err) $log.error('err= ' + err);
        if (data) {
            d.resolve(data);
        }
    });
    return d.promise;
},

The code will look familiar to you by now. We build the params object with the key parameter. Note that the key parameter always needs to be the hash value. In case you defined a RangeKey while creating your table, you will also need to set the RangeKey values while building the params object.

Once the object is ready, we pass it to the getItem method and wait to hear from DynamoDB.

Next, we’ll replace the static data with the actual code within the ProductDetailsCtrl controller as follows:

.controller('ProductDetailsCtrl', ['$scope', '$stateParams', 'AWSservice', '$log',
    function($scope, $stateParams, AWSservice, $log) {
        var id = $stateParams.id;
        AWSservice.getProductDetails(id).then(
            function(data) {
                $scope.product = data.Item;
            }, function(err) {
                $log.error(err);
            });
}
])

Now, we’ll create the partial view that will display the product info. Let’s edit the file called product-details.html with the following short piece of code:

<h1>{{product.title.S}}</h1>
<img ng-src="https://s3.amazonaws.com/garage-commerce/{{product.productPicUrl.S}}">
<p>{{product.description.S}}</p>
<h3>{{product.price.N|currency}}</h3>

Adding products to cart

So, now that we have our product details page ready, we will build the Add to Cart functionality.

Let’s add the Add to Cart button on the product.details.html page as follows:

<button class="btn btn-success" ng-click="addToCart(product.product_id.S)">Add to Cart</button>

Next, let’s add the controller function within the ProductDetailsCtrl controller:

$scope.addToCart=function(product_id){
$scope.shoppingBasket.push(product_id); 
}

For now, we are pushing the product IDs into an angularjs scope. Alternatively, you would want to save this information in another DynamoDB. This will allow you to build further on features such as abandoned carts and also not let you lose out on the scope values each time you refresh the page.

The checkout page

Now that items are getting added to the cart, let’s work on the checkout page. We start by adding this new state in stateProvider in the app.js file as follows:

$stateProvider.state('checkout', {
    url:'/checkout',
    templateUrl: 'partials/checkout.html',
    controller: 'CheckoutCtrl'
});

Make sure that this is your first state in the list of routes or at least above the category state route. If not, angularjs will treat /checkout as another category.

The controller for our checkout page would look like this:

.controller('CheckoutCtrl', ['$scope', 'AWSservice',
    function($scope, AWSservice) {
        $scope.totalPrice = 0;
        $scope.checkoutList = [];
        angular.forEach($scope.shoppingBasket, function(item) {
            AWSservice.getProductDetails(item).then(
                function(data) {
                    var basketItem = {};
                    basketItem.title = data.Item.title.S;
                    basketItem.price = data.Item.price.N;
                    $scope.totalPrice = $scope.totalPrice + parseInt(basketItem.price);
                    $scope.checkoutList.push(basketItem);
                }, function(err) {
                    $log.error(err);
                }
            );
        });
    }
])

We iterate through each item from our shoppingBasket scope object and fire a request to our getProductDetails function to get the details of the product. We then push the title and price into an array, which we’ll call checkoutList.

We’ll now create the partial called checkout.html with the following code:

<h1>Checkout</h1>
<hr/>

<div class="col-md-10">
    <table class="table">
        <thead>
            <tr>
                <th>No.</th>
                <th>Product</th>
                <th>Price</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="item in checkoutList">
                <td>{{$index+1}}</td>
                <td>{{item.title}}</td>
                <td>{{item.price|currency}}</td>
            </tr>
            <tr>
                <td></td>
                <td>
                    <strong>Total</strong>
                </td>
                <td>
                    <strong>{{totalPrice|currency}}</strong>
                </td>
            </tr>
        </tbody>
    </table>
</div>

The code for the partial simply runs an ng-repeat directive to list out all the items in the checkoutList array.

Let’s also add the View Shopping Basket link in our index.html file as highlighted:

<a class=" col-md-2 pull-right" href="#/checkout">View Shopping Basket </a>
  <div ui-view></div>

Saving the orders

The final step would be to save the order details into another table. Let’s create a table on DynamoDB, call it garage-commerce-orders, and set the primary hash key as order_id.

Let’s add our checkout button to our checkout.html partial:

<div class="col-md-1 pull-right">
   <button class="btn btn-success" ng-click="placeOrder()"> Place Order</button>
</div>

The controller function for this within the checkoutCtrl would look like this:

$scope.placeOrder=function(){
    AWSservice.saveOrder($scope.checkoutList,$scope.user.id);
};

Finally, the saveOrder function service in the AWSservice provider will look like this:

saveOrder: function(orders, buyer_id) {
    var orderString = JSON.stringify(orders);
    AWS.config.region = region;
    var timestamp = new Date().getTime();
    var UUID = "#" + buyer_id + "-" + timestamp;

    var dynamo = new AWS.DynamoDB({
        params: {TableName: 'garage-commerce-orders'}
    });
    var orderData = {
        Item:    
        {'order_id': {S:UUID},
        'buyer_id': {S:buyer_id},
        'order_data':{S: orderString}
        }

    };

    dynamo.putItem(orderData, function(err, data) {
        if (err) $log.error(err);

    });

}

With this, we complete our Garage Commerce app. Refresh your browsers, and play around and enjoy.

 

 

In this Tutorial, we saw:

  • How to go about using the AWS services, namely S3 and DynamoDB. We took advantage of the AWS JS SDK to interact with these services and store data in them.
  • We saw how to integrate Facebook and use it with Amazon’s Web Identity Federation to authenticate access to the AWS services.
  • We stored and retrieved data from databases and uploaded files into S3 using pure JavaScript, which I’m sure is a delight for many frontend developers.
  • We saw the problems related to asynchronous calls and saw how to use resolve to ensure that data is preloaded before the route controller function is called.

Like always, there is so much more you can do to further enhance and improve the app that you’ve built. In case you are looking to build on this further, here are a couple of things you can try adding:

  • A payment gateway so that customers can make payments using their credit card. For PayPal, have a look at its Adaptive Payments API at https://developer.paypal.com/docs/classic/adaptive-payments/gs_AdaptivePayments/.
  • You can also look at stripe.com as a payment option.
  • Build out an admin section that allows you to see all the orders and abandoned carts in the system. These would be simple functions that read data out from the DynamoDB database.
  • Try adding a keyword search using angularjs filters.

I hope you enjoyed building your very own e-commerce app

AWS deployment architectures

When deploying applications on AWS, there are numerous ways of deploying the app. One can make use of the services that AWS provides to build an architecture that can maximize performance and reduce costs. We will explore two topologies that can be used to deploy our angularjs apps in the following sections.

The EC2 server-based architecture

The most common deployment architecture for regular web applications would be as follows:

The EC2 server-based architecture
This architecture consists of an EC2 instance, which has the web server running and the application deployed on it.

It will talk to an RDS database to read and write from it. All the static files such as images, CSS, JS, and media files, if any, would be stored in an S3 bucket and directly served to the user’s browser from the S3 bucket or through CloudFront.

This is a common architecture to deploy server-side apps that are built on, say, Ruby on Rails (RoR), PHP, or Java. All requests are sent to the server, which dynamically generates the HTML page by pulling data from the database and placing it into content templates, and sends back the final HTML page to the browser. As you can see, the web server is doing quite a bit of heavy lifting here, and as the number of concurrent users increases, the load on the web server increases proportionately.

In such cases, the logical ways of scaling would be to bump up the commuting power of the EC2 instance, and add multiple EC2 instances under a load balancer. One would make use of AWS’s CloudWatch and autoscaling to carry out the scaling.

The Server-less Architecture

The following topology is another alternate deployment topology on which we can deploy our app. We can make use of and look up to this topology especially to deploy pure client-side apps and in cases where data is static.

The Server-less Architecture
This architecture is often referred to as the Server-less Architecture, primarily because in this topology, the web server is sparingly used, or it can even be fully negated at times.

Here, instead of deploying our app on the EC2 instance, we would deploy it in the S3 bucket along with the images and media files. The JSON data that our angularjs app will read from could also be cached and stored in the S3 bucket.

In this scenario, when the user visits the application for the first time, the app along with the JSON file would get downloaded to the user’s browser, and after that, all the interaction would happen within the browser and the S3 bucket, thus leaving the web server free to handle only critical server-level tasks such as authentication, validation, and so on. We can also make use of CloudFront and have the S3 contents served via CloudFront; this will make our app infinitely scalable as now the CloudFront and S3 become the main points for serving the content. As the traffic increases, it would be divided among the numerous CloudFront nodes. A significant advantage here is that we no longer have a single point of failure for our app. In case one or more of the CloudFront nodes fail, CloudFront would automatically redirect the users to the other available node. Besides these advantages of high scalability and high availability, this architecture also has a very low hosting cost compared to running traditional EC2 instances.