
{"id":2539,"date":"2016-11-03T16:28:22","date_gmt":"2016-11-03T23:28:22","guid":{"rendered":"http:\/\/briantroy.com\/?p=2539"},"modified":"2025-01-03T04:37:28","modified_gmt":"2025-01-03T04:37:28","slug":"series-part-5-serverless-architecture-a-practical-implementation-serverless-web-application","status":"publish","type":"post","link":"https:\/\/blogarchive.briantroy.com\/index.php\/2016\/11\/03\/series-part-5-serverless-architecture-a-practical-implementation-serverless-web-application\/","title":{"rendered":"Series \u2013 Part 5: Serverless Architecture \u2013 a practical implementation: Serverless web application."},"content":{"rendered":"<p>In\u00a0part <a href=\"http:\/\/briantroy.com\/2016\/10\/26\/series-part-4-serverless-architecture-a-practical-implementation-securing-a-serverless-rest-api\/\">four of the series<\/a>\u00a0I discussed securing the serverless REST API serving the collected IoT data from the security camera devices.<\/p>\n<p>In this post I will cover deploying the web application that uses\u00a0the REST API.<\/p>\n<p><!--more-->As a review, the image below provides an overview of the serverless web application that will be implemented using this REST API:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2024 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/10\/serverless-app-arch-001-2.png\" alt=\"serverless-app-arch-001\" width=\"1024\" height=\"768\" \/><\/p>\n<h1>Serverless Web Application<\/h1>\n<p>In this post we will set up a secure web application which allows an authenticated user to list and view the security videos. All of this will be accomplished without the need for a server.<\/p>\n<h2>Step 1: The web application<\/h2>\n<p>Our web application can be found in the <a href=\"https:\/\/github.com\/briantroy\/SecurityVideos\">GitHub repo found here<\/a>. The application is responsive &#8211; using the <a href=\"http:\/\/getskeleton.com\">Skeleton responsive CSS boilerplate<\/a> &#8211; and works great both in browser and on mobile devices. The following is a gallery of application screenshots:<\/p>\n<div id='gallery-1' class='gallery galleryid-2539 gallery-columns-3 gallery-size-thumbnail'><figure class='gallery-item'>\n\t\t\t<div class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/blogarchive.briantroy.com\/index.php\/2016\/11\/03\/series-part-5-serverless-architecture-a-practical-implementation-serverless-web-application\/screenshot-2016-11-03-14-41-56\/'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"140\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-14-41-56-2.png\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" style=\"width:100%;height:93.09%;max-width:2026px;\" \/><\/a>\n\t\t\t<\/div><\/figure><figure class='gallery-item'>\n\t\t\t<div class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/blogarchive.briantroy.com\/index.php\/2016\/11\/03\/series-part-5-serverless-architecture-a-practical-implementation-serverless-web-application\/screenshot-2016-11-03-14-41-43\/'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"137\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-14-41-43-2.png\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" style=\"width:100%;height:91.63%;max-width:2054px;\" \/><\/a>\n\t\t\t<\/div><\/figure><figure class='gallery-item'>\n\t\t\t<div class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/blogarchive.briantroy.com\/index.php\/2016\/11\/03\/series-part-5-serverless-architecture-a-practical-implementation-serverless-web-application\/screenshot-2016-11-03-14-41-29\/'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"133\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-14-41-29-2.png\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" style=\"width:100%;height:88.45%;max-width:2078px;\" \/><\/a>\n\t\t\t<\/div><\/figure><figure class='gallery-item'>\n\t\t\t<div class='gallery-icon landscape'>\n\t\t\t\t<a href='https:\/\/blogarchive.briantroy.com\/index.php\/2016\/11\/03\/series-part-5-serverless-architecture-a-practical-implementation-serverless-web-application\/screenshot-2016-11-03-14-41-13\/'><img loading=\"lazy\" decoding=\"async\" width=\"150\" height=\"131\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-14-41-13-2.png\" class=\"attachment-thumbnail size-thumbnail\" alt=\"\" style=\"width:100%;height:87.16%;max-width:2072px;\" \/><\/a>\n\t\t\t<\/div><\/figure>\n\t\t<\/div>\n\n<p>The application is a single page HTML, CSS and Javascript application. I won&#8217;t go into detail about the code except where relevant to implementation.<\/p>\n<h3>REST API Integration<\/h3>\n<p>The application relies on the REST API we deployed in <a href=\"http:\/\/briantroy.com\/2016\/10\/24\/series-part-3-serverless-architecture-a-practical-implementation-serverless-rest-api\/\">part three<\/a> and <a href=\"http:\/\/briantroy.com\/2016\/10\/26\/series-part-4-serverless-architecture-a-practical-implementation-securing-a-serverless-rest-api\/\">four<\/a> of the series. The API requires a google authentication token for security and the following code provides an example of the REST API integration:<\/p>\n<pre>function getCameraList(token) {\n    $.ajax({\n        url: base_api_uri + \"\/cameras\",\n        crossDomain: true,\n        headers: {\n            \"Authorization\":token\n        },\n\n        success: function( result ) {\n            $(\".navigation\").show();\n            $(\".options\").show();\n            getLatestVideos(user_token);\n            camlist = result;\n            loadCameraVids(result, token);\n        }\n    });\n}\n<\/pre>\n<p>This Javascript uses jQuery to make an XHR request to the REST API. The\u00a0<em>base_api_uri<\/em> is defined at the top of the <a href=\"https:\/\/github.com\/briantroy\/SecurityVideos\/blob\/master\/securityvideo.js\">securityvideo.js<\/a> file found in the repository. This URI is found in your API Gateway Stage deployment discussed in part four:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2399 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/10\/screenshot-2016-10-26-13-16-29-2.png\" alt=\"screenshot-2016-10-26-13-16-29\" width=\"2796\" height=\"1694\" \/><\/p>\n<p>The Invoke URL for your API deployment should be used as the\u00a0<em>base_api_uri<\/em>.<\/p>\n<p>The\u00a0<em>crossDomain<\/em><em>: true<\/em> parameter allows for the cross domain XHR request, and the\u00a0<em>&#8220;Authorization&#8221;: token<\/em> parameter sends the Google Authorization token to API Gateway for use by our custom authorizor created in part four of the series.<\/p>\n<p>On success the function loads the video timeline and the last 5 videos for each camera found in this call. The referenced functions can be found in the securityvideo.js file &#8211; feel free to review that file if you are interested in the exact mechanisms that are used.<\/p>\n<h3>Deploying the Web Application &#8211; Serverless<\/h3>\n<p>In order to deploy our web application we will leverage S3 static web site hosting. The first consideration is the host name you&#8217;ll be using for your web application. In my case I used a sub-domain of my\u00a0<em>brianandkelly.ws <\/em>domain. The exact URL I want to deploy the application to is:<\/p>\n<p>http:\/\/security-videos.brianandkelly.ws<\/p>\n<p>In order to enable S3 static site hosting for that URL create a bucket with the exact host name:\u00a0<em>security-videos.brianandkelly.ws<\/em><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2598 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-15-03-52-2.png\" alt=\"screenshot-2016-11-03-15-03-52\" width=\"2140\" height=\"700\" \/><\/p>\n<p>Now you can enable static site hosting by clicking on the &#8220;Properties&#8221; button at the top:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2605 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-15-05-10-2.png\" alt=\"Screenshot 2016-11-03 15.05.10.png\" width=\"3218\" height=\"1406\" \/><\/p>\n<p>You&#8217;ll select &#8220;Enable website hosting&#8221; and provide an index document and error document.<\/p>\n<p>Next, you&#8217;ll want to add this policy to the &#8220;Permissions&#8221; section:<\/p>\n<pre>{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": [\n\t\t{\n\t\t\t\"Sid\": \"PublicReadGetObject\",\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Principal\": \"*\",\n\t\t\t\"Action\": \"s3:GetObject\",\n\t\t\t\"Resource\": \"arn:aws:s3:::!!Your Bucket Name!!\/*\"\n\t\t}\n\t]\n}\n<\/pre>\n<p>Replacing &#8220;!!Your Bucket Name!! with the name of your S3 bucket.<\/p>\n<p>Last, you&#8217;ll need to add a CNAME entry in your DNS to resolve your host name to the S3 bucket:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2618 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-15-13-44-2.png\" alt=\"screenshot-2016-11-03-15-13-44\" width=\"1462\" height=\"188\" \/><\/p>\n<p>The S3 bucket host name can be found under the &#8220;Static Website Hosting&#8221; section of the bucket properties.<\/p>\n<p>You can now load the content found in the GitHub repo to your S3 bucket and the application will be available at the URL with your bucket name.<\/p>\n<p>However, before you do that, you&#8217;ll need to configure one more item.<\/p>\n<h2>Step 2: Google Auth API<\/h2>\n<p>In order to use the Google OAuth API to authenticate your users with their Google login you&#8217;ll need to create credentials in the google developer console.<\/p>\n<p>To do that go to the <a href=\"https:\/\/console.developers.google.com\/\">google console<\/a>\u00a0&#8211; sign in and select the &#8220;Credentials&#8221; tab on the left side:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2631 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-15-24-14-2.png\" alt=\"screenshot-2016-11-03-15-24-14\" width=\"2456\" height=\"942\" \/><br \/>\nThe select &#8220;Create a project&#8221; and select &#8220;OAuth client ID&#8221;:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2635 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-15-27-09-2.png\" alt=\"Screenshot 2016-11-03 15.27.09.png\" width=\"2460\" height=\"1488\" \/><\/p>\n<p>Next you&#8217;ll give your Credentials a name and provide the URLs to the application:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2638 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-15-37-31-2.png\" alt=\"Screenshot 2016-11-03 15.37.31.png\" width=\"2472\" height=\"1500\" \/><\/p>\n<p>You&#8217;ll want to add two &#8220;Authorized Javascript origins&#8221; &#8211; the actual S3 bucket URL (found in the bucket properties under &#8220;Static Website Hosting&#8221;) and the URL\/Host you configured a CNAME for above.<\/p>\n<p>NOTE: you&#8217;ll need to press the &#8220;Create&#8221; button after entering each URL and then again to create the Credentials &#8211; small Google UX problem there.<\/p>\n<p>Once this is complete you can copy the &#8220;Client ID&#8221; for the Credentials.<\/p>\n<h3>Adding the Google API Credentials to the Web Application<\/h3>\n<p>Now that you have a Client ID you can put it in the HTML for the web application. You&#8217;ll want to edit the i<a href=\"https:\/\/github.com\/briantroy\/SecurityVideos\/blob\/master\/index.html\">ndex.html file found in the repository<\/a> and edit the following meta tag:<\/p>\n<pre>name=\"google-signin-client_id\" content=\"your google client id here\"\n<\/pre>\n<p>Put your google client id in the\u00a0<em>content<\/em> value and save the file.<\/p>\n<p>Assuming you&#8217;ve already changed the\u00a0<em>base_api_uri<\/em> (above) to your API Gateway URI you can move the source to your S3 bucket using the S3 Console or your favorite S3 client (I use <a href=\"https:\/\/cyberduck.io\/\">Cyberduck<\/a>).<\/p>\n<p>You can now test the application by loading the S3 bucket URL in a browser. If you see a simple &#8220;Sign In&#8221; button as follows the application is working:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2661 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-15-53-02-2.png\" alt=\"Screenshot 2016-11-03 15.53.02.png\" width=\"2180\" height=\"328\" \/><\/p>\n<h2>Debugging Tips:<\/h2>\n<p>API Authenticator:<\/p>\n<p>Get the value of the\u00a0<em>token\u00a0<\/em>variable from your favorite web debugger console and use it to test the custom authorizer. This can be done in the API Gateway console:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2669 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-15-56-30-2.png\" alt=\"Screenshot 2016-11-03 15.56.30.png\" width=\"2210\" height=\"1410\" \/><\/p>\n<p>REST API:<\/p>\n<p>Take a look at the Lambda monitoring for each function &#8211; and you can see the raw logging information by selecting &#8220;View logs in CloudWatch&#8221; in the upper right:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2673 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-15-58-16-2.png\" alt=\"screenshot-2016-11-03-15-58-16\" width=\"2516\" height=\"1036\" \/><\/p>\n<p>S3 Static Hosting:<\/p>\n<p>The web responses are very useful, but if you are having issues and need more detailed information turn on logging for the bucket in the S3 console:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2677 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-16-00-11-2.png\" alt=\"screenshot-2016-11-03-16-00-11\" width=\"3110\" height=\"994\" \/><\/p>\n<h1>Summary<\/h1>\n<p>You now have an end to end IoT system that is completely serverless. If you&#8217;ll recall from part one Nest would charge us $2250.00\/year for cloud storage and playback. Here is my October AWS costs in the account hosting this IoT system:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2681 size-full\" src=\"https:\/\/blogarchive.briantroy.com\/wp-content\/uploads\/2016\/11\/screenshot-2016-11-03-16-03-31-2.png\" alt=\"Screenshot 2016-11-03 16.03.31.png\" width=\"2606\" height=\"638\" \/><\/p>\n<p><em><strong>The system handles ~250GB of videos per month for $18.00\/month.<\/strong><\/em><\/p>\n<h2>Complexity<\/h2>\n<p>Having said that, deploying this IoT system took 5 posts in this series (several of which are quite long and dense). This is without going into a lot of detail in many areas.<\/p>\n<h2>Conclusion<\/h2>\n<p>Serverless is still quite complex and requires coordination of multiple services and APIs. The benefits are clear, but significant technical complexity exists in the configuration of the disparate services.<\/p>\n<p>However, the reduction in the amount of custom code required to achieve a highly scalable system is impressive. Having written these types of n-scale systems from scratch I can attest to the fact that this is much simpler and much more cost effective.<\/p>\n<p>We are beginning to see frameworks &#8211;\u00a0<a href=\"https:\/\/serverless.com\">such as the aptly named serverless framework<\/a>\u00a0&#8211; being created which may provide benefits (and drawbacks) in the future. I&#8217;m confident that between the cloud providers (AWS, Google, Microsoft) and the framework developers we will see the complexity reduce &#8211; but this will likely only happen once clear and solid architecture patterns are developed.<\/p>\n<p>If you&#8217;d like my help getting your serverless application architected, designed and built just give me a shout @ <a href=\"https:\/\/partitiontolerance.io\/contact\/\">Partition Tolerance<\/a>\u00a0or on <a href=\"https:\/\/twitter.com\/briantroy\">Twitter @briantroy<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In\u00a0part four of the series\u00a0I discussed securing the serverless REST API serving the collected IoT data from the security camera devices. In this post I will cover deploying the web application that uses\u00a0the REST API.<\/p>\n","protected":false},"author":1,"featured_media":1744,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,11,19,28],"tags":[249],"_links":{"self":[{"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/posts\/2539"}],"collection":[{"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/comments?post=2539"}],"version-history":[{"count":2,"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/posts\/2539\/revisions"}],"predecessor-version":[{"id":3321,"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/posts\/2539\/revisions\/3321"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/media\/1744"}],"wp:attachment":[{"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/media?parent=2539"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/categories?post=2539"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogarchive.briantroy.com\/index.php\/wp-json\/wp\/v2\/tags?post=2539"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}