<?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>Forem: Balaji Sivakumar</title>
    <description>The latest articles on Forem by Balaji Sivakumar (@balaji_sivakumar_e7a4b07a).</description>
    <link>https://forem.com/balaji_sivakumar_e7a4b07a</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%2F3556845%2Faf0e1960-ef50-4641-9aef-6c39051b3a1f.jpg</url>
      <title>Forem: Balaji Sivakumar</title>
      <link>https://forem.com/balaji_sivakumar_e7a4b07a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/balaji_sivakumar_e7a4b07a"/>
    <language>en</language>
    <item>
      <title>🏗️ Part 2 — Running a Serverless API Locally with AWS SAM (API Gateway + Lambda + DynamoDB)</title>
      <dc:creator>Balaji Sivakumar</dc:creator>
      <pubDate>Sun, 16 Nov 2025 04:02:52 +0000</pubDate>
      <link>https://forem.com/balaji_sivakumar_e7a4b07a/part-2-running-a-serverless-api-locally-with-aws-sam-api-gateway-lambda-dynamodb-dob</link>
      <guid>https://forem.com/balaji_sivakumar_e7a4b07a/part-2-running-a-serverless-api-locally-with-aws-sam-api-gateway-lambda-dynamodb-dob</guid>
      <description>&lt;h1&gt;
  
  
  📘 Serverless TODO App — Article Series
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/balaji_sivakumar_e7a4b07a/building-a-serverless-todo-app-with-aws-vercel-my-first-aws-project-1h1m"&gt;Architecture Overview&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/balaji_sivakumar_e7a4b07a/part-2-running-a-serverless-api-locally-with-aws-sam-api-gateway-lambda-dynamodb-dob"&gt;Local Backend with AWS SAM &lt;em&gt;(You are here)&lt;/em&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deploying Backend to AWS (SAM + CDK) - &lt;em&gt;(Coming soon…)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  🏗️ Part 2 — Running a Serverless API Locally with AWS SAM
&lt;/h1&gt;

&lt;h3&gt;
  
  
  (API Gateway + Lambda + DynamoDB Local)
&lt;/h3&gt;

&lt;p&gt;This article expands on &lt;strong&gt;Part 1&lt;/strong&gt;, diving into how the backend works and how to run it &lt;strong&gt;fully locally&lt;/strong&gt; using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS SAM
&lt;/li&gt;
&lt;li&gt;Local Lambda (Docker)
&lt;/li&gt;
&lt;li&gt;DynamoDB Local
&lt;/li&gt;
&lt;li&gt;Seed scripts
&lt;/li&gt;
&lt;li&gt;curl-based testing
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures rapid, cost-free development before deploying to AWS (in Part 3).&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;GitHub Repo:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/balaji-sivakumar-dev/aws-starter-projects/tree/main/aws-sam-gateway-lambda-dynamodb" rel="noopener noreferrer"&gt;aws-sam-gateway-lambda-dynamodb&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Scope: Part 2 is &lt;strong&gt;local-only&lt;/strong&gt;. Cloud deploy, auth, and hardening land in Part 3.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  🧰 Prerequisites for Local-Only
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html" rel="noopener noreferrer"&gt;AWS SAM CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker (for Lambda + DynamoDB Local containers)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.python.org/downloads/" rel="noopener noreferrer"&gt;Python 3.13&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html" rel="noopener noreferrer"&gt;Optional: AWS CLI (not required for local-only; dummy creds work)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  🎯 What This Part Covers
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;How Lambda routing, validation, and DB access work together
&lt;/li&gt;
&lt;li&gt;How SAM emulates API Gateway + Lambda
&lt;/li&gt;
&lt;li&gt;How DynamoDB Local integrates via environment variables
&lt;/li&gt;
&lt;li&gt;Full local run workflow
&lt;/li&gt;
&lt;li&gt;curl commands to test every endpoint
&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  🧭 Why Local-First Development?
&lt;/h1&gt;

&lt;p&gt;Local-first gives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instant iteration
&lt;/li&gt;
&lt;li&gt;No AWS costs
&lt;/li&gt;
&lt;li&gt;Offline development
&lt;/li&gt;
&lt;li&gt;Safe experimentation
&lt;/li&gt;
&lt;li&gt;Faster debugging
&lt;/li&gt;
&lt;li&gt;Confidence before cloud deployment
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SAM provides a &lt;strong&gt;near-identical Lambda runtime&lt;/strong&gt; locally using Docker.&lt;/p&gt;


&lt;h1&gt;
  
  
  🏗️ Architecture Overview &amp;amp; What You’ll Build
&lt;/h1&gt;

&lt;p&gt;Single Lambda handles all &lt;code&gt;/todos&lt;/code&gt; routes, talking to DynamoDB Local via SAM’s local API Gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────┐
│ API Gateway (Local)      │
└──────────────┬───────────┘
               │
        /todos routes
               │
┌──────────────▼───────────┐
│ AWS Lambda (Python 3.13) │  &amp;lt;-- src/app.py
│ Router + Handlers (Local)│
└──────────────┬───────────┘
               │ boto3
┌──────────────▼───────────┐
│ DynamoDB (Local)         │
└──────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  🧩 What the Backend Does
&lt;/h1&gt;

&lt;p&gt;CRUD API for TODO items:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/todos&lt;/td&gt;
&lt;td&gt;List all items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;/todos/{id}&lt;/td&gt;
&lt;td&gt;Get by ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/todos&lt;/td&gt;
&lt;td&gt;Create&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT&lt;/td&gt;
&lt;td&gt;/todos/{id}&lt;/td&gt;
&lt;td&gt;Update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;/todos/{id}&lt;/td&gt;
&lt;td&gt;Delete&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  🧠 Deep Dive — How the Lambda Works
&lt;/h1&gt;

&lt;p&gt;Below are the &lt;strong&gt;essential excerpts&lt;/strong&gt; needed to understand the backend logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;1️⃣ Lambda Router (src/app.py)&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resource&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;httpMethod&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;path_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pathParameters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/todos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;create_todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/todos&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;list_todos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/todos/{id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get_todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/todos/{id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PUT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;update_todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/todos/{id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;http_method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DELETE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;delete_todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not Found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✔ Simple but effective routing&lt;br&gt;&lt;br&gt;
✔ One Lambda for all endpoints&lt;br&gt;&lt;br&gt;
✔ Fast &amp;amp; cost-efficient  &lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;2️⃣ Pydantic Models (src/models.py)&lt;/strong&gt;
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# pending|done
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;✔ Ensures clean validation&lt;br&gt;&lt;br&gt;
✔ Protects API from malformed payloads  &lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;3️⃣ DynamoDB Layer (src/ddb.py)&lt;/strong&gt;
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ddb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dynamodb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;endpoint_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DDB_ENDPOINT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Local or AWS
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ddb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This makes the DB layer &lt;strong&gt;environment-agnostic&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;4️⃣ Example Handler — Create Todo&lt;/strong&gt;
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_todo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TodoCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now_iso&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;updated_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now_iso&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;✔ Validated input&lt;br&gt;&lt;br&gt;
✔ UUID generation&lt;br&gt;&lt;br&gt;
✔ Auto timestamps  &lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;5️⃣ SAM Template — What Each Part Does&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Globals&lt;/strong&gt; – shared settings for every Lambda: Python 3.13 runtime, 20s timeout, and env vars (&lt;code&gt;TABLE_NAME&lt;/code&gt;, &lt;code&gt;DDB_ENDPOINT&lt;/code&gt;) so code can read &lt;code&gt;os.environ&lt;/code&gt; instead of hardcoding values.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TodoTable (DynamoDB)&lt;/strong&gt; – a pay-per-request table named &lt;code&gt;${StackName}-todos&lt;/code&gt; with partition key &lt;code&gt;id&lt;/code&gt; and a &lt;code&gt;status-index&lt;/code&gt; GSI (Global Secondary Index). This is where all TODO items live.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HttpApi (API Gateway)&lt;/strong&gt; – creates the &lt;code&gt;/v1&lt;/code&gt; HTTP API with permissive CORS so you can call it from anywhere during dev.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TodoFunction (Lambda)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CodeUri: src/&lt;/code&gt;, &lt;code&gt;Handler: app.handler&lt;/code&gt; → entry point is &lt;code&gt;src/app.py::handler&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Policies: DynamoDBCrudPolicy&lt;/code&gt; → grants this Lambda CRUD access to &lt;code&gt;TodoTable&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Events&lt;/code&gt; wire HTTP verbs/paths to the single function:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /todos&lt;/code&gt;, &lt;code&gt;GET /todos&lt;/code&gt;, &lt;code&gt;GET /todos/{id}&lt;/code&gt;, &lt;code&gt;PUT /todos/{id}&lt;/code&gt;, &lt;code&gt;DELETE /todos/{id}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DDB_ENDPOINT&lt;/code&gt; is empty in AWS (uses managed DynamoDB). For local runs, set it in &lt;code&gt;env_local.json&lt;/code&gt; to hit DynamoDB Local.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sam local start-api&lt;/code&gt; reads these mappings so local routing mirrors production.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Outputs&lt;/strong&gt; – surface the API URL and table name after deploy so you can copy/paste them into tests or clients.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  🏃‍♂️ Running the Backend Locally
&lt;/h1&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;1️⃣ Start DynamoDB Local&lt;/strong&gt;
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you don’t have AWS creds configured, export dummy values so CLI + seed script work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dummy
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dummy
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ca-central-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;2️⃣ Create &amp;amp; Activate Python Virtual Environment using venv&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;macOS / Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; src/requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows (PowerShell):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;venv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;venv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\venv\Scripts\Activate.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;src/requirements.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deactivate any time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deactivate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;3️⃣ Seed Data&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TABLE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;local-todos
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DDB_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:8000
python3 scripts/seed_local_ddb.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;4️⃣ Build the Project&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;5️⃣ Create env_local.json&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"TodoFunction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"TABLE_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local-todos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"DDB_ENDPOINT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://host.docker.internal:8000"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;6️⃣ Start Local API Gateway&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;start-api &lt;span class="nt"&gt;--env-vars&lt;/span&gt; env_local.json
&lt;span class="c"&gt;# Add --debug to see detailed SAM logs while developing:&lt;/span&gt;
&lt;span class="c"&gt;# sam local start-api --env-vars env_local.json --debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  &lt;strong&gt;7️⃣ Test Endpoints with curl (smoke test)&lt;/strong&gt;
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:3000/todos   &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt;   &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"title":"First Todo","description":"hello"}'&lt;/span&gt; | jq

&lt;span class="c"&gt;# List&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://127.0.0.1:3000/todos | jq

&lt;span class="c"&gt;# Grab an id from the JSON above&lt;/span&gt;
&lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1a31c3fc-45b9-44f7-8152-47d258340d60"&lt;/span&gt;

&lt;span class="c"&gt;# Get one&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://127.0.0.1:3000/todos/&lt;span class="nv"&gt;$ID&lt;/span&gt; | jq

&lt;span class="c"&gt;# Update&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT http://127.0.0.1:3000/todos/&lt;span class="nv"&gt;$ID&lt;/span&gt;   &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt;   &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"status":"done"}'&lt;/span&gt; | jq

&lt;span class="c"&gt;# Delete&lt;/span&gt;
curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE http://127.0.0.1:3000/todos/&lt;span class="nv"&gt;$ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloud next steps (covered in Part 3): &lt;code&gt;sam deploy --guided&lt;/code&gt; with a real DynamoDB table (leave &lt;code&gt;DDB_ENDPOINT&lt;/code&gt; empty).&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;8️⃣ Stop Local Services&lt;/strong&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Stop SAM server: hit &lt;code&gt;Ctrl+C&lt;/code&gt; in the terminal running &lt;code&gt;sam local start-api&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Stop DynamoDB Local:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  docker compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  📚 Additional Resources
&lt;/h1&gt;

&lt;p&gt;GitHub Repository:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/balaji-sivakumar-dev/aws-starter-projects/tree/main/aws-sam-gateway-lambda-dynamodb" rel="noopener noreferrer"&gt;aws-sam-gateway-lambda-dynamodb&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS docs for deeper dives:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/welcome.html" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html" rel="noopener noreferrer"&gt;AWS SAM&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html" rel="noopener noreferrer"&gt;Amazon DynamoDB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  ⏭️ Coming Up in Part 3
&lt;/h1&gt;

&lt;p&gt;Part 3 covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploying to AWS with SAM
&lt;/li&gt;
&lt;li&gt;Deploying to AWS with CDK
&lt;/li&gt;
&lt;li&gt;S3 packaging
&lt;/li&gt;
&lt;li&gt;API Gateway
&lt;/li&gt;
&lt;li&gt;DynamoDB
&lt;/li&gt;
&lt;li&gt;AWS Free Tier vs Always-Free Notes
&lt;/li&gt;
&lt;li&gt;Budget Alerts to avoid costs
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned! 🚀&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>sam</category>
      <category>dynamodb</category>
    </item>
    <item>
      <title>🚀 Building a Serverless TODO App with AWS + Vercel — My First AWS Project</title>
      <dc:creator>Balaji Sivakumar</dc:creator>
      <pubDate>Fri, 10 Oct 2025 03:07:23 +0000</pubDate>
      <link>https://forem.com/balaji_sivakumar_e7a4b07a/building-a-serverless-todo-app-with-aws-vercel-my-first-aws-project-1h1m</link>
      <guid>https://forem.com/balaji_sivakumar_e7a4b07a/building-a-serverless-todo-app-with-aws-vercel-my-first-aws-project-1h1m</guid>
      <description>&lt;h1&gt;
  
  
  📘 Serverless TODO App — Article Series
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/balaji_sivakumar_e7a4b07a/building-a-serverless-todo-app-with-aws-vercel-my-first-aws-project-1h1m"&gt;Architecture Overview &lt;em&gt;(You are here)&lt;/em&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/balaji_sivakumar_e7a4b07a/part-2-running-a-serverless-api-locally-with-aws-sam-api-gateway-lambda-dynamodb-dob"&gt;Local Backend with AWS SAM&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deploying Backend to AWS (SAM + CDK) - &lt;em&gt;(Coming soon…)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🧰 How I Built a Serverless TODO App with AWS Lambda, DynamoDB &amp;amp; Vercel
&lt;/h2&gt;

&lt;p&gt;I’ve always wanted to build a &lt;strong&gt;real full-stack application&lt;/strong&gt; that’s completely &lt;strong&gt;serverless&lt;/strong&gt;, scalable, and affordable to host.&lt;br&gt;&lt;br&gt;
This project marks that milestone — a &lt;strong&gt;simple but powerful TODO App&lt;/strong&gt; built with &lt;strong&gt;React + Vite on Vercel&lt;/strong&gt; (frontend) and an &lt;strong&gt;AWS backend&lt;/strong&gt; powered by &lt;strong&gt;Cognito, Lambda, and DynamoDB&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Motivation
&lt;/h2&gt;

&lt;p&gt;Most of my side projects started as experiments — a script here, a local app there. But I wanted to go one step further and &lt;strong&gt;deploy a working cloud application&lt;/strong&gt; that could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scale without managing servers
&lt;/li&gt;
&lt;li&gt;Stay &lt;em&gt;within free-tier limits&lt;/em&gt; (important for solo devs!)
&lt;/li&gt;
&lt;li&gt;Integrate modern authentication (AWS Cognito)
&lt;/li&gt;
&lt;li&gt;Be fully automated using &lt;strong&gt;Infrastructure as Code&lt;/strong&gt; (AWS CDK)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of learning theory from tutorials, I decided to &lt;strong&gt;build something end-to-end&lt;/strong&gt; — a small, achievable project that would teach me &lt;em&gt;how real serverless systems fit together&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 What I Built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Todo App (Vercel + AWS)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A lightweight yet production-ready &lt;strong&gt;serverless TODO manager&lt;/strong&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚛️ &lt;strong&gt;React + Vite&lt;/strong&gt; SPA hosted on &lt;strong&gt;Vercel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🔐 &lt;strong&gt;AWS Cognito Hosted UI&lt;/strong&gt; for user authentication (email/password for now, social login ready for later via identity providers)
&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;AWS Lambda Function URL&lt;/strong&gt; exposing CRUD endpoints (Create, Read, Update, Delete)
&lt;/li&gt;
&lt;li&gt;💾 &lt;strong&gt;DynamoDB (on-demand mode)&lt;/strong&gt; to store tasks per user
&lt;/li&gt;
&lt;li&gt;🛠 &lt;strong&gt;AWS CDK (TypeScript)&lt;/strong&gt; to automate deployment of all backend infrastructure (Cognito, Lambda, DynamoDB, IAM roles, etc.)
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;AWS Amplify JS (Auth module)&lt;/strong&gt; integrated with the frontend to handle sign-in and token-based API calls
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is &lt;strong&gt;stateless and serverless&lt;/strong&gt;, meaning no EC2, no manual scaling, and no backend servers to patch — ever.&lt;/p&gt;




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

&lt;p&gt;Here’s the flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The React app is deployed to &lt;strong&gt;Vercel&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Users sign in using &lt;strong&gt;Cognito Hosted UI&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Upon login, the app stores a &lt;strong&gt;JWT token&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Every API call goes through the &lt;strong&gt;Lambda Function URL&lt;/strong&gt;, validated using that token.
&lt;/li&gt;
&lt;li&gt;The Lambda reads and writes tasks to &lt;strong&gt;DynamoDB&lt;/strong&gt;, partitioned by &lt;code&gt;userId&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;🧠 &lt;strong&gt;Note:&lt;/strong&gt; Lambda Function URLs are public by default — the token validation happens &lt;em&gt;inside&lt;/em&gt; the Lambda to ensure secure access.&lt;/p&gt;

&lt;p&gt;It’s clean, secure, and built on services that can scale infinitely.&lt;/p&gt;

&lt;h2&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%2Fdkejpjx2z1dtvadeskeb.png" alt="AWS serverless architecture diagram" width="800" height="1028"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  ⚙️ Tech Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React + Vite + AWS Amplify JS (Auth module)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hosting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vercel (free tier)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS Cognito Hosted UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS Lambda (Function URL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DynamoDB (on-demand)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IaC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS CDK (TypeScript)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  💵 AWS Free Tier — Cost &amp;amp; Scalability
&lt;/h2&gt;

&lt;p&gt;One of my key goals was to &lt;strong&gt;stay within AWS’s always-free tier&lt;/strong&gt;, so I could build, test, and learn without worrying about surprise bills.&lt;/p&gt;

&lt;p&gt;Here’s how each service fits into the free tier and what kind of traffic it can handle before costs start:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AWS Service&lt;/th&gt;
&lt;th&gt;Free Tier Limit&lt;/th&gt;
&lt;th&gt;What It Covers&lt;/th&gt;
&lt;th&gt;Practical Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS Lambda&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1M requests/month + 400,000 GB-seconds compute/month&lt;/td&gt;
&lt;td&gt;All function invocations&lt;/td&gt;
&lt;td&gt;Enough for tens of thousands of TODO operations per month. Perfect for hobby apps or small prototypes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon DynamoDB (On-Demand)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;25 GB free storage&lt;/td&gt;
&lt;td&gt;Automatically scales with traffic&lt;/td&gt;
&lt;td&gt;No need to pre-provision read/write capacity — great for unpredictable workloads.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon Cognito&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;50,000 monthly active users (MAUs) free&lt;/td&gt;
&lt;td&gt;Authentication and token management&lt;/td&gt;
&lt;td&gt;Easily supports most side projects and MVPs without cost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS CDK / CloudFormation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Infrastructure management and provisioning&lt;/td&gt;
&lt;td&gt;No runtime cost — only pays for created resources (still free if resources are within free tier).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vercel Hosting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free plan for hobby projects&lt;/td&gt;
&lt;td&gt;Static site hosting and serverless frontend delivery&lt;/td&gt;
&lt;td&gt;Perfect for individual or small-team projects.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS Amplify Hosting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,000 build minutes/month + 5 GB storage (free for first year only)&lt;/td&gt;
&lt;td&gt;Static site hosting on AWS&lt;/td&gt;
&lt;td&gt;Would have kept everything inside AWS, but the free tier is limited to the first year.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS Budget &amp;amp; Cost Monitor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Budget alerts and cost tracking&lt;/td&gt;
&lt;td&gt;Always set up a budget alert (e.g., $1 threshold) to monitor expenses and avoid surprises.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS API Gateway (optional)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1M REST API calls/month&lt;/td&gt;
&lt;td&gt;Only if used instead of Lambda URL&lt;/td&gt;
&lt;td&gt;Useful for advanced routing or throttling setups later.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;🧠 &lt;strong&gt;Tip:&lt;/strong&gt; All of these services automatically scale down when idle, meaning you only pay when your app is being used — ideal for experimentation and side projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CDK is game-changing&lt;/strong&gt; — writing infrastructure in TypeScript feels natural once you get used to it.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel + AWS work beautifully together&lt;/strong&gt; — frontend on Vercel, backend on AWS, no friction.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWT validation&lt;/strong&gt; in Lambda keeps APIs lightweight yet secure.
&lt;/li&gt;
&lt;li&gt;Keeping things &lt;strong&gt;modular&lt;/strong&gt; (frontend + backend folders) makes local dev and deployment easy.
&lt;/li&gt;
&lt;li&gt;Always configure &lt;strong&gt;budget alerts early&lt;/strong&gt; — they’re free and prevent billing surprises.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧭 What’s Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;strong&gt;Google &amp;amp; GitHub sign-in&lt;/strong&gt; via Cognito Identity Providers.
&lt;/li&gt;
&lt;li&gt;Add &lt;strong&gt;real-time updates&lt;/strong&gt; using AWS AppSync or WebSockets.
&lt;/li&gt;
&lt;li&gt;Build &lt;strong&gt;automated deployments&lt;/strong&gt; using GitHub Actions (with AWS OIDC roles, no static keys).
&lt;/li&gt;
&lt;li&gt;Write detailed follow-ups on each part (Cognito setup, CDK deployment, etc.).&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;This project isn’t just a TODO app — it’s a &lt;strong&gt;template for future serverless ideas&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you’re someone who’s been reading about AWS and Vercel but never &lt;em&gt;connected the dots&lt;/em&gt;, this kind of project is the perfect start. It’s small enough to finish, yet rich enough to teach you real-world cloud development.&lt;/p&gt;




&lt;p&gt;✨ Coming Soon&lt;/p&gt;

&lt;p&gt;In my next post, I’ll share a step-by-step guide to set up this project from scratch —&lt;br&gt;&lt;br&gt;
perfect for beginners who want to clone, deploy, and learn while building along.&lt;/p&gt;

&lt;p&gt;Stay tuned! 🚀&lt;/p&gt;

&lt;p&gt;Thanks for reading 🙏&lt;br&gt;&lt;br&gt;
This was my &lt;strong&gt;first post on Dev.to&lt;/strong&gt; — more articles coming soon!&lt;/p&gt;




</description>
      <category>aws</category>
      <category>vercel</category>
      <category>serverless</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
