Serverless Application Deployment using AWS Lambda, API Gateway, and DynamoDB

Objective

Deploy a serverless REST API on AWS that allows users to perform CRUD (Create, Read, Update, Delete) operations on a DynamoDB table. The API is managed via API Gateway, and the business logic is handled using AWS Lambda functions written in Python. Terraform is used to provision all the infrastructure.

Architecture

Task Workflow

1. Setup Environment

2. Define Terraform Configuration

Terraform will be used to define:

3. Create Terraform Files

a. Define AWS Provider (provider.tf)

provider "aws" {
  region = "us-east-1"
}

b. Create IAM Role for Lambda (iam.tf)

resource "aws_iam_role" "lambda_role" {
  name = "serverless_lambda_role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = { Service = "lambda.amazonaws.com" }
    }]
  })
}

resource "aws_iam_policy_attachment" "lambda_dynamodb" {
  name       = "lambda-dynamodb-policy"
  roles      = [aws_iam_role.lambda_role.name]
  policy_arn = "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess"
}

c. Create DynamoDB Table (dynamodb.tf)

resource "aws_dynamodb_table" "users" {
  name           = "users"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "userId"
  attribute {
    name = "userId"
    type = "S"
  }
}

d. Create Lambda Functions (lambda.tf)

resource "aws_lambda_function" "crud_lambda" {
  filename      = "lambda_function.zip"
  function_name = "crudLambda"
  role          = aws_iam_role.lambda_role.arn
  handler       = "lambda_function.lambda_handler"
  runtime       = "python3.9"
  environment {
    variables = {
      TABLE_NAME = aws_dynamodb_table.users.name
    }
  }
}

Python Code for Lambda (lambda_function.py)

This function performs CRUD operations based on API Gateway requests.

import json
import boto3
import os

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ["TABLE_NAME"])

def lambda_handler(event, context):
    http_method = event["httpMethod"]
    body = json.loads(event.get("body", "{}"))
    if http_method == "POST":
        table.put_item(Item=body)
        return {"statusCode": 200, "body": json.dumps("User created")}
    elif http_method == "GET":
        user_id = event["queryStringParameters"]["userId"]
        response = table.get_item(Key={"userId": user_id})
        return {"statusCode": 200, "body": json.dumps(response.get("Item", {}))}
    elif http_method == "PUT":
        user_id = body["userId"]
        table.update_item(
            Key={"userId": user_id},
            UpdateExpression="SET age = :age",
            ExpressionAttributeValues={":age": body["age"]}
        )
        return {"statusCode": 200, "body": json.dumps("User updated")}
    elif http_method == "DELETE":
        user_id = event["queryStringParameters"]["userId"]
        table.delete_item(Key={"userId": user_id})
        return {"statusCode": 200, "body": json.dumps("User deleted")}
    return {"statusCode": 400, "body": json.dumps("Invalid request")}

e. Create API Gateway (api_gateway.tf)

resource "aws_api_gateway_rest_api" "serverless_api" {
  name        = "ServerlessAPI"
  description = "API Gateway for Serverless App"
}

resource "aws_api_gateway_resource" "users_resource" {
  rest_api_id = aws_api_gateway_rest_api.serverless_api.id
  parent_id   = aws_api_gateway_rest_api.serverless_api.root_resource_id
  path_part   = "users"
}

resource "aws_api_gateway_method" "users_method" {
  rest_api_id   = aws_api_gateway_rest_api.serverless_api.id
  resource_id   = aws_api_gateway_resource.users_resource.id
  http_method   = "POST"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "lambda_integration" {
  rest_api_id = aws_api_gateway_rest_api.serverless_api.id
  resource_id = aws_api_gateway_resource.users_resource.id
  http_method = aws_api_gateway_method.users_method.http_method
  integration_http_method = "POST"
  type        = "AWS_PROXY"
  uri         = aws_lambda_function.crud_lambda.invoke_arn
}

4. Deploy the Infrastructure

Run the following commands to deploy:

terraform init
terraform apply -auto-approve

5. Testing the API

Once deployed, get the API Gateway URL from Terraform output and test it using cURL or Postman

Create a User

curl -X POST https://your-api-id.execute-api.us-east-1.amazonaws.com/users \
     -H "Content-Type: application/json" \
     -d '{"userId": "123", "name": "John Doe", "age": 30}'

Retrieve a User

curl -X GET "https://your-api-id.execute-api.us-east-1.amazonaws.com/users?userId=123"

Update a User

curl -X PUT https://your-api-id.execute-api.us-east-1.amazonaws.com/users \
     -H "Content-Type: application/json" \
     -d '{"userId": "123", "age": 31}'

Delete a User

curl -X DELETE "https://your-api-id.execute-api.us-east-1.amazonaws.com/users?userId=123"

6. Clean Up Resources

To delete all AWS resources, run:

terraform destroy -auto-approve

Key Takeaways:

Summary

This project deploys a serverless API with AWS Lambda, API Gateway, and DynamoDB using Terraform. The architecture is scalable, cost-efficient, and fully managed by AWS.