<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dibendu Saha</title>
    <description>The latest articles on DEV Community by Dibendu Saha (@dibendu_saha_).</description>
    <link>https://dev.to/dibendu_saha_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2721471%2Fc92de9d4-dcb6-4cec-be57-e1864dab4fe7.png</url>
      <title>DEV Community: Dibendu Saha</title>
      <link>https://dev.to/dibendu_saha_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dibendu_saha_"/>
    <language>en</language>
    <item>
      <title>From CLF-02 to Real-World AWS Solutions: Hosting my Website on AWS with S3, CloudFront and Beyond</title>
      <dc:creator>Dibendu Saha</dc:creator>
      <pubDate>Thu, 30 Jan 2025 20:13:15 +0000</pubDate>
      <link>https://dev.to/dibendu_saha_/from-clf-02-to-real-world-aws-solutions-hosting-my-website-on-aws-with-s3-cloudfront-and-beyond-26b</link>
      <guid>https://dev.to/dibendu_saha_/from-clf-02-to-real-world-aws-solutions-hosting-my-website-on-aws-with-s3-cloudfront-and-beyond-26b</guid>
      <description>&lt;p&gt;A few months ago, if you had asked me about hosting a website on AWS, I would have probably given you a vague, half-baked answer. Little did I know, I was still far from mastering cloud deployment (and, in some ways, I still am). But to truly crack the code, your knowledge must be quantifiable. At the very least!&lt;/p&gt;

&lt;p&gt;This realization led me to start implementing my knowledge by deploying real-world AWS solutions. And what better way to begin than by migrating my own portfolio website from Azure to AWS?&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Choosing the Hosting Approach
&lt;/h1&gt;

&lt;p&gt;AWS provides multiple ways to host an application, depending on your use case and budget. You could use EC2 instances, Elastic Beanstalk, ECS, or even AWS Amplify for frontend applications. However, for a simple React-based portfolio website, I wanted a serverless and highly available solution.&lt;/p&gt;

&lt;p&gt;That’s where Amazon S3 shines. It’s an object storage service that allows us to host static websites with minimal effort. But hosting the files isn’t enough — we also need CloudFront for content delivery, Lambda for serverless code execution and Certificate Manager for SSL certificate management.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;In order for you to proceed, you need to have –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS root user account&lt;/li&gt;
&lt;li&gt;AWS IAM user account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of the AWS &lt;strong&gt;root user&lt;/strong&gt; account as a master account which has full administrative access to all AWS services and resources. AWS recommends creating an &lt;strong&gt;IAM user&lt;/strong&gt; and giving it the appropriate privilege to perform all the day-to-day activities.&lt;/p&gt;

&lt;p&gt;So, make sure you're logged in as the IAM user to proceed with the tasks below.&lt;/p&gt;

&lt;p&gt;Let’s walk step by step, in detail.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 Static Website
&lt;/h3&gt;

&lt;p&gt;This approach, in its barebone is pretty straightforward — &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the S3 bucket. 

&lt;ul&gt;
&lt;li&gt;Define permissions. &lt;/li&gt;
&lt;li&gt;Enable Static Website Hosting. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Upload the build files.&lt;/li&gt;
&lt;li&gt;Access the application.

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's go over it -&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create the bucket&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From the AWS console, I searched for 'S3' and clicked on &lt;em&gt;Create Bucket&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Entered the bucket name, and this name had to be unique &lt;em&gt;globally&lt;/em&gt;, and not just within my selected region.&lt;/li&gt;
&lt;li&gt;Block Public Access — Turned this off. In other words, enabled public access, given the obvious reason that my website should be accessible out from the internet.&lt;/li&gt;
&lt;li&gt;Left other options as is and created the bucket.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once created, I went to the newly created bucket and -&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;u&gt;Properties&lt;/u&gt; tab. 

&lt;ul&gt;
&lt;li&gt;Enabled &lt;strong&gt;S3 static website hosting&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Hosting type – &lt;em&gt;Host a static website&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;As for the &lt;em&gt;Index document&lt;/em&gt;, I set it to &lt;code&gt;index.html&lt;/code&gt; (or whatever your application entry page is).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Error document&lt;/em&gt; is &lt;em&gt;optional&lt;/em&gt;, but I've set it to &lt;code&gt;index.html&lt;/code&gt;, as well. This serves the purpose of URL redirection since the application routes are managed internally by the React application. &lt;em&gt;Save&lt;/em&gt; the changes.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Next, I headed over to the &lt;u&gt;Permissions&lt;/u&gt; tab.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is where I set the &lt;em&gt;Bucket Policy&lt;/em&gt; to allow read access to my S3 objects.&lt;/li&gt;
&lt;li&gt;Added this bucket policy -     &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodobe66hd7axlhafaguz.png" alt="Bucket policy" width="800" height="502"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make sure to append the wild-card path (/*) at the end of your resource. This allows all objects to be accessible inside your bucket.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But you already turned off the &lt;em&gt;Block Public Access&lt;/em&gt; settings. Why do you require an extra policy to allow access?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Well, turning off the Block Public Access settings just enables us to define the actual policy (or the actual permissions) without which we would end up with the following error –&lt;br&gt;
            &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftofqzzuwsvsra5j0zv4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftofqzzuwsvsra5j0zv4l.png" alt="Bucket policy error" width="800" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Upload the build files&lt;/strong&gt;&lt;br&gt;
Once added, all the necessary configurations for my bucket were ready and then all I did was upload the build files. I eventually automated the deployments with CI/CD using GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Access the application&lt;/strong&gt;&lt;br&gt;
Like I mentioned at the beginning, in its barebone, my website is now ready and if I hit the URL that was generated and displayed at the very end of the &lt;u&gt;Properties&lt;/u&gt; tab, my website was up and running –&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hap13fm9r7pfplsgeci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hap13fm9r7pfplsgeci.png" alt="Bucket website endpoint" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And voila! Or is it?&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudFront
&lt;/h3&gt;

&lt;p&gt;Do you notice something odd with the URL? &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxp2u0nec7dypzq5hp5bp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxp2u0nec7dypzq5hp5bp.png" alt="CloudFront URL" width="800" height="60"&gt;&lt;/a&gt;&lt;br&gt;
Yep, that's a strange looking one. Of course, I did not want that. Plus, the connection isn't secure — there's no valid TLS certificate assigned.&lt;/p&gt;

&lt;p&gt;That's where &lt;u&gt;CloudFront&lt;/u&gt; makes the grand entry. Using CloudFront as a proxy infont of my S3 bucket helped me achieve two things –&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It mitigated the issue of an insecure connection with a free TLS certificate.&lt;/li&gt;
&lt;li&gt;CloudFront is Amazon's CDN service, and hence it deployed and caching my website at edge locations, optimizing load times for end users.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's get started –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From the AWS console, I navigated to 'CloudFront' &amp;gt; Clicked &lt;em&gt;Create distribution&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;For the &lt;u&gt;Origin domain&lt;/u&gt;, I selected the bucket endpoint that I created.&lt;/li&gt;
&lt;li&gt;Since the bucket has been configured for static website hosting, I received a prompt to use the S3 website endpoint instead of the bucket endpoint. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F92tbbxway7lfe1x2c4mq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F92tbbxway7lfe1x2c4mq.png" alt="Use website endpoint" width="800" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I switched to the website endpoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3bmzzttg61nmed9kl9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3bmzzttg61nmed9kl9s.png" alt="Website endpoint" width="800" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Under &lt;u&gt;Default cache behavior&lt;/u&gt;, I set the &lt;em&gt;Viewer protocol policy&lt;/em&gt; to &lt;em&gt;Redirect HTTP to HTTPS&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;You can enable or disable &lt;em&gt;Web Application Firewall (WAF)&lt;/em&gt; based on your requirements but for my website, I did not need it — plus enabling it incurs additional cost.&lt;/li&gt;
&lt;li&gt;Under &lt;u&gt;Settings&lt;/u&gt;, I set the &lt;em&gt;Default root object&lt;/em&gt; to &lt;code&gt;index.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Left all other configurations to its defaults.&lt;/li&gt;
&lt;li&gt;Created the distribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the CloudFront distribution was up and ready, I was able use the distribution URL to open my website. The URL looks something like this – &lt;code&gt;https://xxxxxxxxx.cloudfront.net&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Notice how the URL is HTTPS-enabled, by default. Much better but still isn't ideal. Don't worry, I'll circle back to this later.&lt;br&gt;
&lt;br&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking Visitor Counts with DynamoDB and Lambda
&lt;/h2&gt;

&lt;p&gt;Next, I wanted to track the visitor count to my website and display it in my website. For this, I used –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;u&gt;Amazon DynamoDB&lt;/u&gt; - Database to store the visitor count data.&lt;/li&gt;
&lt;li&gt;
&lt;u&gt;AWS Lambda&lt;/u&gt; - Serverless code that updates and retrieves this data.&lt;/li&gt;
&lt;li&gt;
&lt;u&gt;Amazon API Gateway&lt;/u&gt; - Route requests to the Lambda function.

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DynamoDB
&lt;/h3&gt;

&lt;p&gt;DynamoDB is Amazon's fully managed, NoSQL database, which stores data in a &lt;em&gt;key-value pair&lt;/em&gt;. For my purpose, I'll store the &lt;em&gt;current visitor count&lt;/em&gt; and the &lt;em&gt;created date&lt;/em&gt;, i.e., the date and time of visit. &lt;/p&gt;

&lt;p&gt;I could just make an initial entry and keep updating the count as and when there are new visits, but instead, I'll keep inserting new records each time a user visits my website just so that it acts as a ledger/transaction table.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From the AWS console, I navigated to 'DynamoDB'.&lt;/li&gt;
&lt;li&gt;On the left pane, &lt;em&gt;Tables&lt;/em&gt; &amp;gt; &lt;em&gt;Create table&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Entered the &lt;em&gt;Table name&lt;/em&gt; and the &lt;em&gt;Partition key&lt;/em&gt;. Consider this as the primary key.&lt;/li&gt;
&lt;li&gt;I'm going to name it – 

&lt;ul&gt;
&lt;li&gt;Table name – &lt;strong&gt;PortfolioVisitorCount&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Partition key – &lt;strong&gt;TotalCount&lt;/strong&gt; of type &lt;em&gt;Number&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;I skipped the &lt;em&gt;Sort key&lt;/em&gt;, but I wanted a second key (column) — &lt;strong&gt;CreatedDate&lt;/strong&gt;, which will be automatically created on the first database transaction.&lt;/li&gt;

&lt;li&gt;Left the rest settings to the defaults and &lt;em&gt;Create table&lt;/em&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Next up,&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda
&lt;/h3&gt;

&lt;p&gt;AWS Lambda is a serverless computing service from AWS that runs code in response to events or triggers. For my DynamoDB to insert and retrieve records, I needed a Lambda function which would talk to my database.&lt;/p&gt;

&lt;p&gt;Here's how I created the Lambda function –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigated to 'Lambda' in the console.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Create function&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;You can select any of the three options for the source code based upon your convenience –

&lt;ul&gt;
&lt;li&gt;Author from scratch&lt;/li&gt;
&lt;li&gt;Use a blueprint&lt;/li&gt;
&lt;li&gt;Container image&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;I chose the 2nd option and selected the below blueprint –
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxxptmk54bt93e4n27lo.png" alt="Lambda blueprint" width="800" height="83"&gt;
&lt;/li&gt;

&lt;li&gt;Provided the &lt;em&gt;Function name&lt;/em&gt;.&lt;/li&gt;

&lt;li&gt;For the &lt;em&gt;Execution role&lt;/em&gt;, I chose — &lt;em&gt;Create a new role from AWS policy templates&lt;/em&gt;.&lt;/li&gt;

&lt;li&gt;Entered the &lt;em&gt;Role name&lt;/em&gt; and selected the &lt;em&gt;Policy template&lt;/em&gt; — &lt;em&gt;Simple microservice permissions&lt;/em&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdflmev3a8l7i4mcow58m.png" alt="Policy template" width="800" height="215"&gt;
&lt;/li&gt;

&lt;li&gt;We'll be provided with the default source code for the basic CRUD operation.&lt;/li&gt;

&lt;li&gt;You can edit the source code to fit your requirement. For the purpose of this blog, the actual source code is out of context.&lt;/li&gt;

&lt;li&gt;I removed the &lt;em&gt;API Gateway trigger&lt;/em&gt; configuration for now — I'll come back to it at a later stage.&lt;/li&gt;

&lt;li&gt;And &lt;em&gt;Create function&lt;/em&gt;.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Once the Lambda function was created, I navigated to the function. Under the &lt;em&gt;Code&lt;/em&gt; tab, I see my function source code. I can modify the code as per requirement and test it.&lt;/p&gt;

&lt;p&gt;Alternatively, you can migrate this code to a function in your local machine and instead upload the code from the top-right corner –&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76mfo0a3csnq4z91u1dx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F76mfo0a3csnq4z91u1dx.png" alt="Upload code" width="800" height="187"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  API Gateway
&lt;/h3&gt;

&lt;p&gt;Now that I had my Lambda function in place, I needed a way to trigger it. While I could create its &lt;em&gt;Function URL&lt;/em&gt; and expose it over the internet, it is generally not a recommended approach, due to its lack of basic features like rate limiting, authentication and authorization and even security. &lt;/p&gt;

&lt;p&gt;I used &lt;em&gt;Amazon API Gateway&lt;/em&gt; for this purpose. This acts as a "front door" to my Lambda function.  This way, in the near future, if I've more Lambda functions, this  &lt;em&gt;API Gateway&lt;/em&gt; will connect them all.&lt;/p&gt;

&lt;p&gt;Let's get started...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigated to &lt;em&gt;API Gateway&lt;/em&gt; from the console &amp;gt; &lt;em&gt;Create API&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;I used REST API &amp;gt; &lt;em&gt;Build&lt;/em&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqda1j02oc0ku4xrmgli3.png" alt="REST API" width="800" height="266"&gt;
&lt;/li&gt;
&lt;li&gt;Selected &lt;em&gt;New API&lt;/em&gt;, provided the &lt;em&gt;API name&lt;/em&gt; (&lt;em&gt;Description&lt;/em&gt; is optional).&lt;/li&gt;
&lt;li&gt;Chose &lt;em&gt;Regional&lt;/em&gt; as the &lt;em&gt;API endpoint type&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Create API&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Once created &amp;gt; &lt;em&gt;Create resource&lt;/em&gt; on the left-pane.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ef7du3rylhgn2ayespd.png" alt="Create resource" width="800" height="276"&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;u&gt;Create resource&lt;/u&gt; page –

&lt;ul&gt;
&lt;li&gt;Set the &lt;em&gt;Resource path&lt;/em&gt; and &lt;em&gt;Resource name&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Enabled CORS.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhdajdewr10xjd9nv770.png" alt="Create resource page" width="800" height="206"&gt;
&lt;/li&gt;
&lt;li&gt;Created the resource.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;Once the resource was created, I went to the newly-created resource on the left-pane, then &lt;em&gt;Create method&lt;/em&gt; on the right-pane.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fab6myv93aqb85cmbjxq9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fab6myv93aqb85cmbjxq9.png" alt="Create method" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;em&gt;Create method&lt;/em&gt; page –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chose the &lt;em&gt;Method type&lt;/em&gt; as &lt;em&gt;any&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Chose the &lt;em&gt;Integration type&lt;/em&gt; as &lt;em&gt;Lambda function&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enabled &lt;em&gt;Lambda proxy integration&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But why the proxy integration?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In layman's terms — proxy integration makes the API Gateway act as a proxy passing on the entire HTTP request to the Lambda function as-is (method, headers, query parameters, body, etc.). That way, my Lambda function has more flexibility and control over the request and response header, and now it is the Lambda function's responsibility to process the request and return a properly formatted response.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provided the &lt;em&gt;Lambda function&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Left other settings to its defaults.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Create method&lt;/em&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnf3wbkyptx7p9b715nn8.png" alt="Create method page" width="800" height="480"&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;That completed my API Gateway setup. But not just yet!&lt;/p&gt;

&lt;p&gt;It's just the setup — I still had to &lt;u&gt;deploy&lt;/u&gt; it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But why the extra step of deploying? Where was this step with all the previous AWS resources?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Amazon API Gateway differentiates &lt;strong&gt;design&lt;/strong&gt; from &lt;strong&gt;implementation&lt;/strong&gt;. Since, it has a lot of moving parts, it supports the concept of &lt;strong&gt;stages&lt;/strong&gt; (&lt;em&gt;dev, test, prod, etc.&lt;/em&gt;). This is done so that live systems are not affected while I test my current API changes, before deploying it.&lt;/p&gt;

&lt;p&gt;Let's deploy the API Gateway –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Clicked &lt;em&gt;Deploy API&lt;/em&gt; on the top-right corner.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7e2cthhggpcdu7b9x6z6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7e2cthhggpcdu7b9x6z6.png" alt="Deploy API" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Selected &lt;em&gt;New stage&lt;/em&gt; for &lt;em&gt;Stage&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provided &lt;em&gt;prod&lt;/em&gt; for the &lt;em&gt;Stage name&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Create stage&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that I've deployed my API to the newly created stage, I can see that under &lt;em&gt;Stages&lt;/em&gt; on the left pane. The &lt;em&gt;Invoke URL&lt;/em&gt; (API endpoint) to be consumed is mentioned here –&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu14mzvjeltyz6qeyad5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu14mzvjeltyz6qeyad5o.png" alt="Invoke URL" width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make sure that my API Gateway was connected to my Lambda function, I tested the Lambda integration –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigated to &lt;em&gt;Resources&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Selected the method.&lt;/li&gt;
&lt;li&gt;On the right-pane, clicked the &lt;em&gt;Test&lt;/em&gt; tab.&lt;/li&gt;
&lt;li&gt;Filled in the details &amp;gt; &lt;em&gt;Test&lt;/em&gt;. 
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8nxh1pkrj00vadmqvihc.png" alt="Test Lambda" width="800" height="309"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an additional step, I tested the API Gateway itself via triggering its &lt;em&gt;Invoke URL&lt;/em&gt; from Postman.&lt;/p&gt;

&lt;p&gt;Next thing — I consumed this endpoint from my React codebase, but like I said, for the purpose of this blog, this is out of context but by now, my website has been integrated with the API gateway.&lt;/p&gt;

&lt;p&gt;Right — now that I've all the pieces assembled together and have a perfectly running website, it was time for me to share it online and with friends &amp;amp; family.&lt;/p&gt;

&lt;p&gt;So, I shared my website URL —  &lt;code&gt;https://d45uiv9deg2fhr.cloudfront.net&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Only to realize that it was gibberish, let alone professional — not one person would bother remembering this URL.&lt;/p&gt;

&lt;p&gt;And so, I wanted a custom domain for my website. You can purchase it from &lt;em&gt;Amazon Route53&lt;/em&gt; or any other 3rd-party registrar, but I had purchased mine from &lt;em&gt;namecheap.com&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not from Amazon Route53?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Remember when I mentioned in the Introduction that I was "transferring" my portfolio website from Azure to AWS? Yeah, I had purchased my domain during the time my website was hosted on Azure, and hence I did not bother transferring it to Route53.&lt;/p&gt;

&lt;p&gt;Now that I already had a domain purchased, I wanted a public certificate from &lt;em&gt;AWS Certificate Manager&lt;/em&gt;. This was required to obtain a publicly trusted SSL/TLS certificate for securing my website.&lt;/p&gt;

&lt;p&gt;In case you're wondering —&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But you already have a SSL certificate from CloudFront.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, only if you use the CloudFront-generated URL. If you plan to use a custom domain, you need to request a SSL/TLS certificate separately.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate Manager
&lt;/h3&gt;

&lt;p&gt;Now that I had to request a certificate, the first important step I did was change my region to &lt;strong&gt;us-east-1&lt;/strong&gt; (N.Virginia)?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since CloudFront is a global service, it is not tied to a particular region, unlike other AWS resources. For this, it requires certificates to be issued in the &lt;em&gt;us-east-1&lt;/em&gt; region, and it uses certificates issued in this region to distribute content securely across all edge locations worldwide. Below is a resource from AWS that can help –&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-and-https-requirements.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-and-https-requirements.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once set, I requested a public certificate –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS console &amp;gt; &lt;em&gt;Certificate Manager&lt;/em&gt; &amp;gt; &lt;em&gt;Request&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Selected the &lt;em&gt;Request a public certificate&lt;/em&gt; option.&lt;/li&gt;
&lt;li&gt;In the &lt;em&gt;Request public certificate&lt;/em&gt; page –

&lt;ul&gt;
&lt;li&gt;Entered my domain and sub-domain names.&lt;/li&gt;
&lt;li&gt;Set &lt;em&gt;Validation method&lt;/em&gt; as &lt;em&gt;DNS validation&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Left the rest settings to defaults.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77769v71wki1pic24ff2.png" alt="Request certificate" width="800" height="332"&gt;
&lt;/li&gt;
&lt;li&gt;Clicked on &lt;em&gt;Request&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;I had the certificate requested. However, it was not validated. I followed the steps for my domain registrar (namecheap.com) on how to validate the certificate.&lt;/li&gt;

&lt;li&gt;&lt;p&gt;In essence, I added two records in the &lt;u&gt;Advanced DNS&lt;/u&gt; section of my domain registrar with the generated &lt;em&gt;CNAME name&lt;/em&gt; and &lt;em&gt;CNAME value&lt;/em&gt; in AWS Certificate Manager –&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ljg2ouizotfy86ibc5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ljg2ouizotfy86ibc5j.png" alt="CNAME data" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Once added, ACM triggered a validation, with a successful validation looking like this –&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26jdpn79jxxwykz7sa3c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26jdpn79jxxwykz7sa3c.png" alt="ACM successful validation" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Once validated, I went....&lt;/p&gt;

&lt;h3&gt;
  
  
  Back to CloudFront...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;And to my distribution.&lt;/li&gt;
&lt;li&gt;Under the &lt;em&gt;General&lt;/em&gt; tab &amp;gt; &lt;em&gt;Settings&lt;/em&gt; card &amp;gt; &lt;em&gt;Edit&lt;/em&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbeh9b2s1smv6gsk5cfq.png" alt="Update CloudFront domain" width="800" height="340"&gt;
&lt;/li&gt;
&lt;li&gt;Under the &lt;em&gt;Edit settings&lt;/em&gt; page –

&lt;ul&gt;
&lt;li&gt;Added the &lt;em&gt;Alternate domain name&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Chose the &lt;em&gt;Custom SSL certificate&lt;/em&gt; which I just created.&lt;/li&gt;
&lt;li&gt;Selected &lt;em&gt;TLSv1.2_2021&lt;/em&gt; as the &lt;em&gt;Security policy&lt;/em&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1d1s4jj1sq3ejdulhgw.png" alt="CloudFront edit fields" width="800" height="578"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Save changes&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;And there you have it — a secure website with our own custom domain.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing File Downloads
&lt;/h3&gt;

&lt;p&gt;Since the website represents my portfolio, I also implemented the AWS infrastructure to support my resume downloads. &lt;/p&gt;

&lt;p&gt;The resources used for this are – &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;u&gt;S3 bucket&lt;/u&gt; - Holds the resume file.&lt;/li&gt;
&lt;li&gt;
&lt;u&gt;AWS Lambda&lt;/u&gt; - Serverless code execution that retrieves and downloads the file.&lt;/li&gt;
&lt;li&gt;Existing &lt;u&gt;Amazon API Gateway&lt;/u&gt; - Route requests to my Lambda function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'll be concise here but for the &lt;em&gt;S3 bucket&lt;/em&gt;, the approach for this was pretty much the same as with the previous S3 bucket –&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Except that I kept the &lt;em&gt;Static website hosting&lt;/em&gt; disabled. &lt;/li&gt;
&lt;li&gt;For the bucket policy, I granted it read access but only from the Lambda function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As for the &lt;em&gt;Lambda&lt;/em&gt; function, the source code gets the file from the S3 bucket and passes it on to the existing &lt;em&gt;API Gateway&lt;/em&gt; which, in turn, sends back the response to the client.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Architecture
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A picture speaks louder than words.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's sum up what I did so far with an architectural diagram –&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44frt5n4p0nd7hl3xot1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44frt5n4p0nd7hl3xot1.png" alt="Architecture diagram" width="800" height="472"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform: Automating AWS Infrastructure
&lt;/h2&gt;

&lt;p&gt;When I started out to build the AWS ecosystem, there were numerous occasions where I had to delete and recreate the AWS resources owing to various factors — sometimes, it was just to try out a different approach — other times, it was due to some configuration mistake which I wanted to rectify by recreating those resources.&lt;/p&gt;

&lt;p&gt;Manually deleting and recreating those resources was quite a cumbersome process, and more often than not, people just give up thinking of the vast amount of time they would waste in those efforts.&lt;/p&gt;

&lt;p&gt;A better approach to create and manage these infrastructure would be via &lt;u&gt;Infrastructure-as-Code&lt;/u&gt; (IaC), where we write (or configure) the AWS resources as code. That way, we have the code ready for all the AWS resources, and the resources creation (or deletion) is just a script away.&lt;/p&gt;

&lt;p&gt;While AWS provides its own IaC tool — &lt;em&gt;AWS CloudFormation&lt;/em&gt;, it is only limited to AWS. &lt;/p&gt;

&lt;p&gt;A popular option in this space is &lt;strong&gt;Terraform&lt;/strong&gt; which can be used across multiple cloud providers. I used Terraform's &lt;em&gt;HCL (HashiCorp Configuration Language)&lt;/em&gt; to configure my AWS resources.&lt;/p&gt;

&lt;p&gt;This way, I was able to test the AWS resources and its integration multiple times using various configuration without having to manually hand-craft the resources from the console.&lt;/p&gt;

&lt;p&gt;Let's reserve the details for this in another blog.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Hosting a website on AWS might seem daunting at first, but breaking it down into smaller steps makes it manageable. By leveraging S3, CloudFront, DynamoDB, Lambda, and API Gateway, you can build a scalable, secure, and performant web application.&lt;/p&gt;

&lt;p&gt;If you’re just starting with AWS, I highly recommend getting hands-on experience by building projects like this. It’s the best way to solidify your knowledge and stand out in the job market.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Drop me a 'Hi'
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Have questions about my approach to AWS solutions?&lt;/strong&gt;&lt;br&gt;
Let’s connect on &lt;a href="https://www.linkedin.com/in/dibendu-saha/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; — I’d love to hear from you!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explore My Coding Space...&lt;/strong&gt;&lt;br&gt;
Check out my projects and contributions on &lt;a href="https://github.com/Dibendu-Saha" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visit Me...&lt;/strong&gt;&lt;br&gt;
Feel free to explore more about me on my &lt;a href="https://dibendusaha.com/" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Happy Learning. Cheers!
&lt;/h3&gt;

</description>
      <category>aws</category>
      <category>s3</category>
      <category>cloudfront</category>
      <category>lambda</category>
    </item>
  </channel>
</rss>
