ServerlessArchitecture

Building a Serverless REST API with API Gateway, Lambda, and DynamoDB

NT

Naveen Teja

3/2/2026

Building a Serverless REST API with API Gateway, Lambda, and DynamoDB

The serverless stack of API Gateway + Lambda + DynamoDB is one of the most searched architectural patterns on AWS. When implemented correctly, this combination eliminates server management entirely, scales to millions of requests automatically, and costs nearly zero at low traffic volumes due to pay-per-invocation pricing.

The architecture works as follows: API Gateway acts as the front door, handling authentication (via Cognito or Lambda authorizers), request validation, throttling, and CORS. It forwards validated requests to Lambda, which contains your business logic. Lambda reads and writes to DynamoDB using the AWS SDK and returns structured responses back through API Gateway to the client.

The most common mistakes when building this stack are: not enabling DynamoDB point-in-time recovery, neglecting to set Lambda reserved concurrency limits (which can drain your account concurrency budget), and building chatty APIs that call DynamoDB once per item instead of using BatchGetItem. The Terraform below provisions the complete stack — DynamoDB table with on-demand billing, a Lambda function with least-privilege IAM, and API Gateway with Lambda proxy integration.

serverless-api.tf
resource "aws_dynamodb_table" "items" {
  name           = "api-items"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "PK"
  range_key      = "SK"

  attribute {
    name = "PK"
    type = "S"
  }
  attribute {
    name = "SK"
    type = "S"
  }

  point_in_time_recovery {
    enabled = true
  }
}

resource "aws_lambda_function" "api_handler" {
  filename      = "function.zip"
  function_name = "api-items-handler"
  role          = aws_iam_role.lambda_exec.arn
  handler       = "index.handler"
  runtime       = "nodejs20.x"

  reserved_concurrent_executions = 100

  environment {
    variables = {
      TABLE_NAME = aws_dynamodb_table.items.name
    }
  }
}

resource "aws_apigatewayv2_api" "http_api" {
  name          = "items-api"
  protocol_type = "HTTP"

  cors_configuration {
    allow_origins = ["https://www.naveenteja.cloud"]
    allow_methods = ["GET", "POST", "DELETE"]
    allow_headers = ["Content-Type", "Authorization"]
  }
}

resource "aws_apigatewayv2_integration" "lambda" {
  api_id             = aws_apigatewayv2_api.http_api.id
  integration_type   = "AWS_PROXY"
  integration_uri    = aws_lambda_function.api_handler.invoke_arn
}