← Selected work
AWS Serverless · Docker · CI/CD · Project 8

Document Intelligence Pipeline

Upload any PDF — the pipeline automatically extracts text and uses Bedrock Claude to analyze it. Built entirely on AWS serverless services: S3, Lambda, Bedrock, DynamoDB, and API Gateway. No server running 24/7. Everything triggered by events.

AWS Lambda · S3 · Bedrock · DynamoDB · API Gateway · Docker · Node.js GitHub ↗
New Concepts This Project
AWS Lambda — serverless function, runs only when triggered, no server needed. S3 Event Trigger — upload PDF → S3 automatically fires Lambda. No manual trigger. AWS Bedrock — same Claude AI but running inside your AWS account. Data never leaves AWS. DynamoDB — NoSQL database, perfect for Lambda (no persistent connections needed). Event-driven architecture — services talk to each other via events, not direct calls.

What It Does — Simple Explanation

You upload invoice.pdf
        ↓
App reads all the text from PDF
        ↓
AI analyzes it and tells you:
  "This is an invoice from Acme Corp
   Amount: $4,500
   Due date: Aug 30, 2026
   Action: Payment required"
        ↓
Results shown in browser ✅

No button clicking to "process"
No waiting for manual trigger
Upload happens → processing starts automatically ✅

Why AWS? — What's Different From Projects 1-7

Projects 1-7 (what you know):
  Your laptop → Node.js server running 24/7
  React → http://localhost:3006 → Express → Anthropic API

  Problems at scale:
    Server always running → paying even when idle ❌
    One server → can't handle 1000 users at once ❌
    You manage everything → patches, restarts, scaling ❌

Project 8 (AWS serverless):
  No server running 24/7 ✅
  Lambda runs ONLY when PDF uploaded ✅
  AWS handles scaling automatically ✅
  Pay only when code actually runs ✅
  1 user or 1 million → same setup ✅

AWS Services — What Each One Does

📦 Amazon S3 — Simple Storage Service
What it is:
  File storage on AWS
  Like Google Drive but for your code/apps

What it does in P8:
  Stores uploaded PDF files permanently
  Triggers Lambda automatically when file arrives

Why not store locally?
  Local file → server restarts → file gone ❌
  S3 → permanent storage → never lost ✅
  S3 → triggers Lambda automatically ✅

Real world analogy:
  S3 = a smart mailbox
  When mail arrives → mailbox rings a bell
  Bell = S3 event trigger
  Bell wakes up Lambda ✅

Cost: First 5GB free, then $0.023/GB
⚡ AWS Lambda — Serverless Functions
What it is:
  A function that runs on AWS
  No server needed — AWS manages everything

Compare with Express (what you know):

  Express server (P1-P7):
    const app = express();
    app.post("/process", handler);
    app.listen(3006);  ← always running

  Lambda (P8):
    export const handler = async (event) => {
      // same logic here
      return { statusCode: 200 };
    };
    // No app.listen() — AWS calls handler() for you

Key difference:
  Express → runs 24/7 waiting for requests
  Lambda  → sleeps → wakes when S3 uploads PDF
            runs → sleeps again ✅

Cost: First 1 million requests FREE every month
🧠 AWS Bedrock — AI Models Inside AWS
What it is:
  Same Claude AI we use in P1-P7
  But running INSIDE your AWS account

Compare:
  Projects 1-7:
    Your code → Anthropic API (public internet)
    Anthropic servers can see your data ⚠️

  Project 8:
    Your code → AWS Bedrock (inside AWS)
    Data never leaves your AWS account ✅
    HIPAA compliant ✅
    Enterprise ready ✅

Same Claude model, same responses
Just different where it runs

Why this matters:
  Companies with sensitive data (medical, legal, financial)
  CANNOT send data to public Anthropic API
  Must use Bedrock or Azure OpenAI ✅

Model we use: us.anthropic.claude-sonnet-4-6
🗄️ AWS DynamoDB — NoSQL Database
What it is:
  AWS managed NoSQL database
  Key-value store (like a giant JSON object)

Compare with Postgres (what you know):

  Postgres (P3, P5, P7):
    Tables with fixed columns
    SQL queries: SELECT * FROM table WHERE...
    Needs persistent connection
    Complex setup with Lambda

  DynamoDB (P8):
    No fixed schema — store any JSON
    Simple: get item by ID
    HTTP-based → perfect for Lambda ✅
    No connection management needed ✅

Why DynamoDB for Lambda (not Postgres)?
  Lambda starts/stops constantly
  Opening new Postgres connection every time = slow ❌
  DynamoDB = HTTP call = no connection needed ✅

What we store:
  {
    documentId:    "uuid-123",
    fileName:      "invoice.pdf",
    status:        "completed",
    extractedText: "Invoice from Acme...",
    analysis: {
      documentType: "invoice",
      summary:      "Invoice for $4,500...",
      amounts:      ["$4,500"],
      dueDate:      "Aug 30, 2026"
    }
  }

Cost: First 25GB free forever
🌐 API Gateway — REST Endpoints Without Server
What it is:
  Creates HTTP endpoints that trigger Lambda
  Replaces Express routes

Compare:

  Express (P1-P7):
    app.get("/document/:id", handler);
    → http://localhost:3006/document/123

  API Gateway (P8):
    GET /document/{id} → triggers Lambda
    → https://abc123.execute-api.us-east-1.amazonaws.com/document/123

React calls AWS URL instead of localhost
Everything else is the same ✅

Why API Gateway?
  Lambda has no URL by default
  API Gateway gives Lambda a public URL
  Handles: routing, auth, rate limiting, CORS

Complete Architecture

React UI (browser)
  │
  ├── Upload PDF
  │     ↓
  │   AWS S3 (stores file)
  │     ↓ event trigger (automatic!)
  │   AWS Lambda (processor)
  │     ↓ reads PDF
  │   pdf-parse (extract text)
  │     ↓ text
  │   AWS Bedrock Claude (analyze)
  │     ↓ analysis JSON
  │   AWS DynamoDB (save results)
  │
  └── View Results
        ↓
      API Gateway (HTTP endpoint)
        ↓
      AWS Lambda (reader)
        ↓
      AWS DynamoDB (fetch results)
        ↓
      React UI (display) ✅

Event-Driven Architecture — The Key Concept

Old way (what we did in P1-P7):
  User clicks button
  → POST /process request
  → Express server receives
  → Server processes
  → Server responds
  → User sees result

  Problem: Server must be running 24/7
           Waiting for requests
           Paying even when idle

New way (P8 — event-driven):
  User uploads PDF to S3
  → S3 detects new file (EVENT)
  → S3 fires Lambda (REACTION)
  → Lambda processes
  → Saves to DynamoDB

  No one waiting
  No server running
  Just events and reactions ✅

Real world analogy:
  Old: Security guard standing at door 24/7
  New: Motion sensor — only triggers when someone arrives
       Same result, much more efficient ✅

Lambda vs Express — Side by Side

Feature           Express (P1-P7)        Lambda (P8)
────────────────────────────────────────────────────────
Always running    YES ❌                 NO ✅
Server needed     YES ❌                 NO ✅
Triggered by      HTTP request only      Anything (S3, HTTP, timer)
Scales            Manual ❌              Auto ✅
Cost              Always paying          Pay per execution
Code style        app.listen(3006)       export const handler
Entry point       HTTP request           event object
Response          res.json()             return {}
Localhost test    YES ✅                 NO (deploy to test)
Port number       3006, 3007 etc         None ✅

Lambda Code — High Level

// index.mjs — our Lambda function

export const handler = async (event) => {

  // 1. Get PDF details from S3 event
  //    S3 tells us: which bucket, which file
  const bucket   = event.Records[0].s3.bucket.name;
  const fileName = event.Records[0].s3.object.key;

  // 2. Save "processing" status to DynamoDB
  //    So React UI can show "processing..."
  await saveStatus(documentId, "processing");

  // 3. Download PDF from S3 + extract text
  //    pdf-parse reads all text from PDF
  const text = await extractTextFromPDF(bucket, fileName);

  // 4. Send text to Bedrock Claude for analysis
  //    Same as calling Anthropic API in P1-P7
  //    But running inside AWS
  const analysis = await analyzeWithBedrock(text);

  // 5. Save results to DynamoDB
  //    React UI will read from here
  await saveResults(documentId, text, analysis);

  // 6. Done!
  return { statusCode: 200 };
};

// Notice:
// No app.listen() ✅
// No Express ✅
// No port number ✅
// AWS calls handler() when PDF uploaded ✅

How Lambda Executes Without a Server

You deploy code to AWS (zip file):
  aws lambda create-function
    --zip-file fileb://lambda.zip
        ↓
AWS stores your code internally
NOT running — just stored ✅
        ↓
You upload PDF to S3
        ↓
S3 sends event to Lambda:
  {
    Records: [{
      s3: {
        bucket: { name: "mahesh-document-intelligence" },
        object: { key: "invoice.pdf" }
      }
    }]
  }
        ↓
AWS spins up a container:
  → Unzips your lambda.zip
  → Starts Node.js 20 runtime
  → Loads index.mjs
  → Calls handler(event)
        ↓
Your code runs:
  → Reads PDF from S3
  → Calls Bedrock
  → Saves to DynamoDB
        ↓
Container sleeps (stays warm 15 min)
Next upload → reuses same container → faster ✅

AWS manages the server ✅
You never see it ✅
You never pay for idle time ✅

Cold Start vs Warm Start

Cold Start (first request or after 15 min idle):
  AWS spins up container     ~200ms
  Downloads your code        ~100ms
  Starts Node.js             ~200ms
  Runs handler()             ~5000ms (Bedrock call)
  Total:                     ~5.5 seconds

Warm Start (container still alive):
  Reuses container           0ms
  Runs handler()             ~5000ms (Bedrock call)
  Total:                     ~5 seconds

Real bottleneck = Bedrock API call (not cold start)
For document processing: cold start doesn't matter much ✅

IAM Role — Why Lambda Needs Permission

Lambda needs permission to use other AWS services.
Without permission → "Access Denied" error ❌

We created: document-intelligence-role

Permissions attached:
  AWSLambdaBasicExecutionRole  → write logs to CloudWatch
  AmazonS3ReadOnlyAccess       → read PDF from S3
  AmazonBedrockFullAccess      → call Claude via Bedrock
  AmazonDynamoDBFullAccess     → read/write results

Real world analogy:
  Lambda = new employee
  IAM Role = employee badge
  Badge gives access to:
    S3 room (read files) ✅
    Bedrock room (call AI) ✅
    DynamoDB room (save results) ✅

Without badge → doors won't open ❌
With badge → full access ✅

DynamoDB vs Postgres — Why We Changed

Projects 1-7 used Neon Postgres:
  → SQL queries ✅
  → Relationships between tables ✅
  → pgvector for embeddings ✅
  → Needs persistent connection ⚠️

Problem with Postgres + Lambda:
  Lambda starts fresh every time
  Opening new Postgres connection = 100-500ms overhead
  Under high load = too many connections ❌

DynamoDB solves this:
  HTTP-based (like an API call)
  No persistent connection needed
  Lambda calls DynamoDB like a REST API
  Perfect for serverless ✅

Rule:
  Always-on server → Postgres ✅
  Serverless Lambda → DynamoDB ✅

S3 Event Trigger — How It Works

We configured S3 to watch for PDF uploads:

  "When any .pdf file is uploaded
   → automatically call Lambda"

Filter:
  Only .pdf files trigger Lambda
  .jpg, .docx, .txt → ignored ✅

The trigger flow:
  You upload invoice.pdf
        ↓
  S3 detects: new .pdf file!
        ↓
  S3 looks up: who to notify?
        ↓
  S3 calls: document-intelligence Lambda
        ↓
  Passes event: { bucket, key, size, timestamp }
        ↓
  Lambda wakes up → processes PDF ✅

No code needed to check S3
No polling ("is there a new file?")
Event-driven = instant reaction ✅

Bedrock vs Anthropic API — Key Difference

Projects 1-7 (Anthropic public API):
  Your code
    ↓ HTTPS
  api.anthropic.com (public internet)
    ↓
  Anthropic servers process request
  Anthropic can see your data ⚠️

Project 8 (AWS Bedrock):
  Your code (inside AWS)
    ↓ private AWS network
  AWS Bedrock (inside your AWS account)
    ↓
  Claude processes request
  Data never leaves AWS ✅

Same Claude model
Same responses
Different data privacy ✅

When to use Bedrock:
  → Sensitive documents (medical, legal, financial)
  → Enterprise compliance requirements
  → HIPAA, SOC2, GDPR requirements
  → When data must stay in your cloud

Model ID for Bedrock:
  us.anthropic.claude-sonnet-4-6
  ↑ "us." prefix = US inference profile
  Required for on-demand usage

Deploying Lambda — No Localhost

Projects 1-7 development workflow:
  Edit code
  → node index.js
  → test at localhost:3006 ✅
  → instant feedback

Lambda development workflow:
  Edit code
  → zip folder
  → aws lambda update-function-code
  → upload to AWS (~30 seconds)
  → test via AWS Console
  → check CloudWatch logs

No localhost for Lambda because:
  Needs real S3, real Bedrock, real DynamoDB
  These only exist on AWS
  Cannot run locally ❌

Deployment command:
  aws lambda update-function-code
    --function-name document-intelligence
    --zip-file fileb://lambda.zip

Takes ~30 seconds → Lambda updated ✅

CloudWatch — Seeing Your Logs

Projects 1-7:
  console.log("Processing...") → terminal window

Lambda:
  console.log("Processing...") → CloudWatch logs

CloudWatch = AWS logging service
Every console.log in Lambda appears here

How to view:
  AWS Console → CloudWatch
  → Log groups
  → /aws/lambda/document-intelligence
  → Click latest log stream
  → See all logs ✅

Our logs show:
  📥 Event received
  📄 Processing: invoice.pdf
  💾 Saving initial record...
  🔍 Extracting text from PDF...
  ✅ Extracted 1,234 characters
  🧠 Analyzing with Bedrock Claude...
  ✅ Analysis complete
  💾 Saving results to DynamoDB...
  ✅ Document processed successfully

What Claude Returns — Example Analysis

Input: Upload invoice.pdf

Claude's analysis (saved to DynamoDB):
{
  "documentType": "invoice",
  "summary": "Invoice from Acme Corp for software
              development services totaling $4,500,
              due August 30, 2026.",
  "keyFields": {
    "vendor":      "Acme Corp",
    "invoiceNo":   "INV-2026-0892",
    "amount":      "$4,500",
    "dueDate":     "August 30, 2026",
    "services":    "Software development services"
  },
  "importantDates": ["August 30, 2026"],
  "amounts":        ["$4,500"],
  "actionItems":    ["Payment due August 30, 2026"]
}

What's Next — Remaining Work

✅ Built:
  S3 bucket          → stores PDFs
  Lambda function    → processes PDFs
  S3 → Lambda trigger → auto fires
  Bedrock Claude     → analyzes PDFs
  DynamoDB           → stores results

⏳ Coming:
  API Gateway        → REST endpoint for React
  React UI           → upload PDF + view results
  Docker + ECR       → containerize Lambda
  GitHub Actions     → CI/CD auto-deploy

When complete:
  Full serverless AWS pipeline ✅
  Resume: "AWS Lambda, S3, Bedrock,
           DynamoDB, API Gateway,
           Docker, ECR, GitHub Actions CI/CD" ✅
Why This Project Matters for Your Career

Every enterprise company uses AWS. Lambda, S3, DynamoDB, and API Gateway are the most asked-about services in interviews. Before P8 you could only talk about Cloudflare and Anthropic. After P8 you can speak to AWS serverless architecture from hands-on experience — not theory. The event-driven pattern (S3 trigger → Lambda → DynamoDB) is used by companies like Netflix, Airbnb, and thousands of startups to process millions of files daily.