DEV Community

Cover image for Powering Your App with Serverless APIs: AWS API Gateway & Lambda Integration

Powering Your App with Serverless APIs: AWS API Gateway & Lambda Integration

Table of Contents


Introduction

Quick Links:

In Part 1, we launched a beautiful contact form website using S3 and CloudFront. But there's a problem – our form doesn't actually do anything yet. Click "Send Message" and... nothing happens. The data just vanishes into the void.

Today, we're going to change that.

Imagine you're building a lead generation system for your business. Every contact matters. You need a reliable, scalable backend that can handle one request or a million – without breaking a sweat or your budget. That's exactly what API Gateway and Lambda deliver.

In this second part of our Serverless Web Mastery series, we'll transform our static website into a dynamic application by creating a fully-functional REST API. By the end of this post, you'll have an API that can create, read, update, and delete leads – all without managing a single server.

Let's make that form come alive!


Why API Gateway + Lambda?

Before diving into code, let's understand why this combination is so powerful:

πŸ”Œ API Gateway Benefits

Feature What It Does
Managed Infrastructure No servers to patch or scale
Built-in Security API keys, throttling, WAF integration
Request Validation Validate requests before hitting Lambda
CORS Handling Browser security headers, automatic OPTIONS
Monitoring CloudWatch integration out of the box

⚑ Lambda Benefits

Feature What It Does
Pay-per-use Billed only when your code runs
Auto-scaling 0 to thousands of concurrent executions
No Cold Servers No idle instances burning money
Multiple Runtimes Node.js, Python, Go, Rust, and more
Integration Direct access to 200+ AWS services

πŸ’° The Cost Advantage

Traditional server: ~$40-100/month (even when idle)
Serverless (1000 requests/day): ~$0.50/month

That's a 99% cost reduction for most small to medium workloads!


What We're Building

Our Contact Form API will have these endpoints:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       API Endpoints                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚   GET    /health         β†’ Health check (monitoring)        β”‚
β”‚   POST   /leads          β†’ Create a new lead                β”‚
β”‚   GET    /leads          β†’ List all leads (admin)           β”‚
β”‚   GET    /leads/{id}     β†’ Get specific lead                β”‚
β”‚   PUT    /leads/{id}     β†’ Update a lead                    β”‚
β”‚   DELETE /leads/{id}     β†’ Delete a lead                    β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Architecture Overview:

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚   CloudFront    β”‚
                    β”‚  (Part 1 Site)  β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
                             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    API Gateway                        β”‚
β”‚                   (REST API)                          β”‚
β”‚                                                       β”‚
β”‚  β€’ CORS configuration                                 β”‚
β”‚  β€’ Request throttling                                 β”‚
β”‚  β€’ CloudWatch logging                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β–Ό                 β–Ό                 β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   Health    β”‚   β”‚    Leads    β”‚   β”‚   Future    β”‚
    β”‚   Handler   β”‚   β”‚   Handler   β”‚   β”‚  Handlers   β”‚
    β”‚   (Lambda)  β”‚   β”‚   (Lambda)  β”‚   β”‚   (Lambda)  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Project Setup

Let's create the Part 2 directory structure:

cd aws-serverless-website-tutorial
mkdir -p part-2-api-lambda/{cdk,lambda/handlers}
cd part-2-api-lambda/cdk
Enter fullscreen mode Exit fullscreen mode

Create package.json:

{
  "name": "api-lambda-cdk",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc",
    "deploy": "cdk deploy",
    "destroy": "cdk destroy"
  },
  "dependencies": {
    "aws-cdk-lib": "^2.170.0",
    "constructs": "^10.4.2"
  },
  "devDependencies": {
    "@types/node": "^22.10.0",
    "typescript": "~5.7.0",
    "aws-cdk": "^2.170.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Install dependencies:

npm install
Enter fullscreen mode Exit fullscreen mode

Creating Lambda Functions

Health Check Handler

A simple health check is essential for monitoring. Create lambda/handlers/health.ts:

import {
  APIGatewayProxyEvent,
  APIGatewayProxyResult,
  Context,
} from "aws-lambda";

interface HealthResponse {
  status: "healthy" | "degraded" | "unhealthy";
  timestamp: string;
  version: string;
  region: string;
}

export const handler = async (
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<APIGatewayProxyResult> => {
  console.log("Health check requested", {
    requestId: context.awsRequestId,
  });

  const response: HealthResponse = {
    status: "healthy",
    timestamp: new Date().toISOString(),
    version: "1.0.0",
    region: process.env.AWS_REGION || "unknown",
  };

  return {
    statusCode: 200,
    headers: {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": "*",
    },
    body: JSON.stringify(response),
  };
};
Enter fullscreen mode Exit fullscreen mode

Why a health endpoint?

  • Load balancers need it
  • Monitoring systems poll it
  • Quick verification during deployment

Leads Handler

Now for the main event – our leads CRUD handler. Create lambda/handlers/leads.ts:

import {
  APIGatewayProxyEvent,
  APIGatewayProxyResult,
  Context,
} from "aws-lambda";

// Lead interface
interface Lead {
  id: string;
  name: string;
  email: string;
  company?: string;
  subject: string;
  message: string;
  status: "new" | "contacted" | "qualified" | "converted";
  createdAt: string;
  updatedAt: string;
}

// In-memory storage (replaced with DynamoDB in Part 3)
const leadsStorage: Map<string, Lead> = new Map();

// Generate unique ID
function generateId(): string {
  return `lead_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
}

// Validate create request
function validateCreateRequest(body: any): { valid: boolean; error?: string } {
  if (!body.name || body.name.length < 2) {
    return { valid: false, error: "Name is required (min 2 characters)" };
  }
  if (!body.email || !body.email.includes("@")) {
    return { valid: false, error: "Valid email is required" };
  }
  if (!body.message || body.message.length < 10) {
    return { valid: false, error: "Message is required (min 10 characters)" };
  }
  return { valid: true };
}

// Create standardized response
function createResponse(
  statusCode: number,
  body: object,
): APIGatewayProxyResult {
  return {
    statusCode,
    headers: {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Headers": "Content-Type",
      "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS",
    },
    body: JSON.stringify(body),
  };
}

// Main handler
export const handler = async (
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<APIGatewayProxyResult> => {
  const { httpMethod, pathParameters, body } = event;
  const leadId = pathParameters?.id;

  console.log("Request:", {
    method: httpMethod,
    leadId,
    requestId: context.awsRequestId,
  });

  try {
    switch (httpMethod) {
      // Create new lead
      case "POST": {
        const parsedBody = JSON.parse(body || "{}");
        const validation = validateCreateRequest(parsedBody);

        if (!validation.valid) {
          return createResponse(400, {
            success: false,
            error: validation.error,
          });
        }

        const now = new Date().toISOString();
        const lead: Lead = {
          id: generateId(),
          name: parsedBody.name.trim(),
          email: parsedBody.email.toLowerCase().trim(),
          company: parsedBody.company?.trim(),
          subject: parsedBody.subject || "general",
          message: parsedBody.message.trim(),
          status: "new",
          createdAt: now,
          updatedAt: now,
        };

        leadsStorage.set(lead.id, lead);
        console.log("Lead created:", lead.id);

        return createResponse(201, {
          success: true,
          data: lead,
          message: "Lead created successfully",
        });
      }

      // List all leads
      case "GET": {
        if (leadId) {
          const lead = leadsStorage.get(leadId);
          if (!lead) {
            return createResponse(404, {
              success: false,
              error: "Lead not found",
            });
          }
          return createResponse(200, { success: true, data: lead });
        }

        const leads = Array.from(leadsStorage.values());
        return createResponse(200, {
          success: true,
          data: leads,
          message: `Found ${leads.length} lead(s)`,
        });
      }

      // Update lead
      case "PUT": {
        if (!leadId) {
          return createResponse(400, {
            success: false,
            error: "Lead ID required",
          });
        }

        const lead = leadsStorage.get(leadId);
        if (!lead) {
          return createResponse(404, {
            success: false,
            error: "Lead not found",
          });
        }

        const updates = JSON.parse(body || "{}");
        const updatedLead: Lead = {
          ...lead,
          ...updates,
          updatedAt: new Date().toISOString(),
        };

        leadsStorage.set(leadId, updatedLead);
        return createResponse(200, { success: true, data: updatedLead });
      }

      // Delete lead
      case "DELETE": {
        if (!leadId) {
          return createResponse(400, {
            success: false,
            error: "Lead ID required",
          });
        }

        const deleted = leadsStorage.delete(leadId);
        if (!deleted) {
          return createResponse(404, {
            success: false,
            error: "Lead not found",
          });
        }

        return createResponse(200, { success: true, message: "Lead deleted" });
      }

      default:
        return createResponse(405, {
          success: false,
          error: "Method not allowed",
        });
    }
  } catch (error) {
    console.error("Error:", error);
    return createResponse(500, {
      success: false,
      error: "Internal server error",
    });
  }
};
Enter fullscreen mode Exit fullscreen mode

Important Note: This uses in-memory storage for simplicity. In Part 3, we'll replace this with DynamoDB for persistent storage.


Building the API Gateway

Now let's wire everything together with CDK. Create lib/api-lambda-stack.ts:

import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as logs from "aws-cdk-lib/aws-logs";
import * as lambdaNodejs from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";
import * as path from "path";

export class ApiLambdaStack extends cdk.Stack {
  public readonly api: apigateway.RestApi;

  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ============================================
    // Lambda Functions
    // ============================================

    // Health Check Handler
    const healthHandler = new lambdaNodejs.NodejsFunction(
      this,
      "HealthHandler",
      {
        runtime: lambda.Runtime.NODEJS_22_X,
        entry: path.join(__dirname, "../../lambda/handlers/health.ts"),
        handler: "handler",
        description: "Health check endpoint",
        timeout: cdk.Duration.seconds(10),
        memorySize: 128,
        logRetention: logs.RetentionDays.ONE_WEEK,
        // Environment variables
        environment: {
          NODE_ENV: "production",
          LOG_LEVEL: "INFO",
        },
        bundling: {
          minify: true,
          sourceMap: true,
        },
      },
    );

    // Leads Handler
    const leadsHandler = new lambdaNodejs.NodejsFunction(this, "LeadsHandler", {
      runtime: lambda.Runtime.NODEJS_22_X,
      entry: path.join(__dirname, "../../lambda/handlers/leads.ts"),
      handler: "handler",
      description: "Leads CRUD operations",
      timeout: cdk.Duration.seconds(30),
      memorySize: 256,
      logRetention: logs.RetentionDays.ONE_WEEK,
      environment: {
        NODE_ENV: "production",
        LOG_LEVEL: "INFO",
      },
      bundling: {
        minify: true,
        sourceMap: true,
      },
    });

    // ============================================
    // API Gateway
    // ============================================
    this.api = new apigateway.RestApi(this, "ContactFormApi", {
      restApiName: "Contact Form API",
      description: "Serverless API for contact form",

      deployOptions: {
        stageName: "prod",
        loggingLevel: apigateway.MethodLoggingLevel.INFO,
        metricsEnabled: true,
        throttlingBurstLimit: 100,
        throttlingRateLimit: 50,
      },

      // CORS - Critical for browser requests!
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS,
        allowHeaders: ["Content-Type", "Authorization"],
        allowCredentials: true,
      },
    });

    // ============================================
    // API Routes
    // ============================================

    // Health endpoint: GET /health
    const apiResource = this.api.root.addResource('api');
    const healthResource = apiResource.addResource('health');
    healthResource.addMethod('GET', new apigateway.LambdaIntegration(healthHandler, {
        proxy: true, // Use Lambda Proxy Integration
    }));

    // Leads endpoints
    const leadsResource = apiResource.addResource('leads');

    // POST /leads - Create a new lead
    leadsResource.addMethod('POST', new apigateway.LambdaIntegration(this.leadsHandler, {
        proxy: true,
    }));

    // GET /leads - List all leads
    leadsResource.addMethod('GET', new apigateway.LambdaIntegration(this.leadsHandler, {
        proxy: true,
    }));

    // Single lead endpoints: /leads/{id}
    const leadByIdResource = leadsResource.addResource('{id}');

    // GET /leads/{id} - Get specific lead
    leadByIdResource.addMethod('GET', new apigateway.LambdaIntegration(this.leadsHandler, {
        proxy: true,
    }));

    // PUT /leads/{id} - Update a lead
    leadByIdResource.addMethod('PUT', new apigateway.LambdaIntegration(this.leadsHandler, {
        proxy: true,
    }));

    // DELETE /leads/{id} - Delete a lead
    leadByIdResource.addMethod('DELETE', new apigateway.LambdaIntegration(this.leadsHandler, {
        proxy: true,
    }));

    // ============================================
    // Outputs
    // ============================================
    new cdk.CfnOutput(this, "ApiEndpoint", {
      value: this.api.url,
      description: "API Gateway endpoint URL",
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Deep Dive: Proxy vs. Non-Proxy Integration

You'll notice we used { proxy: true } in our Lambda integration. What does this mean?

Lambda Proxy Integration (Recommended)

  • How it works: API Gateway passes the entire HTTP request (headers, body, query params) to your Lambda function as a JSON object.
  • Your Responsibility: Your Lambda must return a response in a specific format: { statusCode: 200, body: "..." }.
  • Pros: Full control over the response, access to all request details, easier to test locally.

Non-Proxy Integration

  • How it works: API Gateway transforms the incoming request using "Mapping Templates" before sending it to Lambda, and transforms the Lambda's response before sending it back to the client.
  • Pros: Decouples your backend logic from HTTP details.
  • Cons: Velocity Template Language (VTL) is hard to debug and maintain.

For modern serverless applications, Proxy Integration is the standard because it keeps the infrastructure simple (no VTL) and moves the logic into the code (TypeScript), which is where we want it!


CORS Configuration Deep Dive

CORS (Cross-Origin Resource Sharing) is often the #1 source of frustration when building APIs. Let me save you hours of debugging:

What is CORS?

When your frontend (hosted on d123.cloudfront.net) makes a request to your API (abc.execute-api.amazonaws.com), browsers block this by default. CORS headers tell the browser "it's okay, allow this request."

What Headers Do We Need?

'Access-Control-Allow-Origin': '*'  // or specific domain
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS'
'Access-Control-Allow-Headers': 'Content-Type,Authorization'
'Access-Control-Allow-Credentials': 'true'  // if using cookies
Enter fullscreen mode Exit fullscreen mode

API Gateway CORS vs Lambda CORS

You need CORS in both places:

  1. API Gateway - Handles OPTIONS preflight requests
  2. Lambda Response - Includes headers in actual responses
// In CDK (API Gateway level)
defaultCorsPreflightOptions: {
  allowOrigins: apigateway.Cors.ALL_ORIGINS,
  allowMethods: apigateway.Cors.ALL_METHODS,
}

// In Lambda (Response level)
return {
  headers: {
    'Access-Control-Allow-Origin': '*',
  },
  // ... rest of response
};
Enter fullscreen mode Exit fullscreen mode

Production Tip

Replace * with your specific domain:

allowOrigins: ['https://yourdomain.com'],
Enter fullscreen mode Exit fullscreen mode

Deployment

Deploy your API:

cd part-2-api-lambda/cdk
npm install
npx cdk deploy
Enter fullscreen mode Exit fullscreen mode

You'll see outputs like:

βœ…  ContactFormApiStack

Outputs:
ContactFormApiStack.ApiEndpoint = https://abc123xyz.execute-api.us-east-1.amazonaws.com/prod/
Enter fullscreen mode Exit fullscreen mode

πŸŽ‰ Your API is live!


Testing Your API

Using curl

# Health check
curl https://YOUR_API/health

# Create a lead
curl -X POST https://YOUR_API/leads \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Doe",
    "email": "john@example.com",
    "subject": "general",
    "message": "Testing the API!"
  }'

# List leads
curl https://YOUR_API/leads
Enter fullscreen mode Exit fullscreen mode

Using Postman

Import this collection for easy testing:

  1. Create new request
  2. Set URL to your API endpoint
  3. Set method (GET, POST, etc.)
  4. For POST/PUT, add JSON body
  5. Send!

Connecting to the Frontend

Update your Part 1 frontend app.js:

const CONFIG = {
  // Replace with your actual API endpoint
  API_ENDPOINT: "https://abc123xyz.execute-api.us-east-1.amazonaws.com/prod",
};

async function submitToAPI(formData) {
  const response = await fetch(`${CONFIG.API_ENDPOINT}/leads`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(formData),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error || "Failed to submit form");
  }

  return response.json();
}
Enter fullscreen mode Exit fullscreen mode

Now your form actually works! πŸŽ‰


Gotchas & Common Pitfalls

1. CORS Errors

Problem: "Access to fetch has been blocked by CORS policy"

Solution: Check both:

  • API Gateway CORS configuration
  • Lambda response headers
// Make sure both have matching headers!
Enter fullscreen mode Exit fullscreen mode

2. 502 Bad Gateway

Problem: API returns 502 error

Causes:

  • Lambda threw an unhandled exception
  • Lambda timed out
  • Response format incorrect

Solution: Check CloudWatch logs:

aws logs tail /aws/lambda/YourFunctionName --follow
Enter fullscreen mode Exit fullscreen mode

3. Lambda Proxy Integration Response Format

Problem: API Gateway doesn't understand Lambda response

Solution: Always return this exact format:

return {
  statusCode: 200,  // Number, not string!
  headers: { ... },
  body: JSON.stringify({ ... }),  // Must be string!
};
Enter fullscreen mode Exit fullscreen mode

4. Path Parameters Not Working

Problem: pathParameters is null or undefined

Solution: Ensure the path parameter is defined in API Gateway:

const leadById = leads.addResource("{id}"); // Note the curly braces!
Enter fullscreen mode Exit fullscreen mode

Best Practices

1. Use Lambda Proxy Integration

Always use proxy integration for flexibility:

new apigateway.LambdaIntegration(handler, {
  proxy: true, // This is the default, but be explicit
});
Enter fullscreen mode Exit fullscreen mode

2. Implement Request Validation

Validate early, fail fast:

function validateRequest(body: any): { valid: boolean; error?: string } {
  if (!body.email) return { valid: false, error: "Email required" };
  if (!isValidEmail(body.email))
    return { valid: false, error: "Invalid email" };
  // ... more validations
  return { valid: true };
}
Enter fullscreen mode Exit fullscreen mode

3. Consistent Error Responses

Use a standard error format:

interface ErrorResponse {
  success: false;
  error: string;
  code?: string;
}
Enter fullscreen mode Exit fullscreen mode

4. Add Request Throttling

Protect your API from abuse:

deployOptions: {
  throttlingBurstLimit: 100,  // Max concurrent requests
  throttlingRateLimit: 50,    // Requests per second
}
Enter fullscreen mode Exit fullscreen mode

5. Enable CloudWatch Logging

Debugging is impossible without logs:

logRetention: logs.RetentionDays.ONE_WEEK,
Enter fullscreen mode Exit fullscreen mode

Cost Considerations

Let's break down the costs:

Service Free Tier Cost After
API Gateway 1M requests/month $3.50 per million
Lambda Requests 1M requests/month $0.20 per million
Lambda Duration 400K GB-seconds ~$0.0000167/GB-second
CloudWatch Logs 5GB ingestion/month $0.50/GB

Real-World Example

For a contact form receiving 1,000 submissions/day:

  • API Gateway: 30K requests/month β†’ Free
  • Lambda: 30K invocations, ~100ms each β†’ Free
  • Total: $0/month (within free tier!)

Even at 100K submissions/day, you're looking at ~$5/month.


Conclusion

Congratulations! πŸŽ‰ You've built a production-ready REST API with:

βœ… Multiple Lambda functions
βœ… RESTful API Gateway endpoints
βœ… CORS configuration for browser access
βœ… Input validation
βœ… Error handling
βœ… CloudWatch logging
βœ… Request throttling

But wait – there's a catch. Our current implementation uses in-memory storage, which means data is lost when Lambda recycles.

In Part 3, we'll fix this by adding DynamoDB for persistent storage. You'll learn:

  • DynamoDB table design
  • CRUD operations with AWS SDK v3
  • Batch operations for performance (check out my DynamoDB Batch Operations blog)
  • Proper error handling and retries

If you're stuck on any CDK issues, my AWS CDK Debugging Guide has got you covered.

GitHub Repository: aws-serverless-website-tutorial

See you until next time. Happy coding! πŸš€


References

Top comments (0)