An AWS Lambda + API Gateway serverless architecture lets you run a REST API with zero servers to patch or scale — API Gateway handles routing and auth, Lambda executes your business logic on demand, and you pay only for actual invocations. This guide walks through building one end to end: function, API, IAM role, and deployment.

Key Takeaways

  • API Gateway handles HTTP routing, request validation, throttling, and auth; Lambda holds your business logic.
  • Lambda needs an execution role with least-privilege permissions — not broad admin access.
  • Lambda proxy integration forwards the full request to your function and lets your code control the response shape.
  • Use stages (dev/prod) and custom domain + Route 53 for production-grade API versioning and branding.
  • Cold starts matter for latency-sensitive APIs — mitigate with provisioned concurrency on hot paths, not everywhere.

Step 1: Write the Lambda Function

# handler.py — a simple Lambda handler for API Gateway proxy integration
import json

def lambda_handler(event, context):
    name = event.get("queryStringParameters", {}).get("name", "World")
    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({"message": f"Hello, {name}!"})
    }

Package and prepare for deployment:

# Zip the function for deployment
zip function.zip handler.py

Step 2: Create the Execution Role (Least Privilege)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}
# Create the role and attach the policy above as an inline policy
aws iam create-role \
  --role-name lambda-api-exec-role \
  --assume-role-policy-document file://trust-policy.json

aws iam put-role-policy \
  --role-name lambda-api-exec-role \
  --policy-name lambda-logging \
  --policy-document file://exec-policy.json

Only add extra permissions (DynamoDB, S3, etc.) as your function actually needs them — starting from CloudWatch Logs-only access is the correct default.

Step 3: Deploy the Function and Wire Up API Gateway

# Create the Lambda function
aws lambda create-function \
  --function-name greet-api \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-api-exec-role \
  --handler handler.lambda_handler \
  --zip-file fileb://function.zip
# Create a REST API and a proxy resource/method pointing at the Lambda
aws apigateway create-rest-api --name "greet-api-gw"
# (capture the returned API id as $API_ID)

aws apigateway get-resources --rest-api-id $API_ID
# (capture root resource id as $ROOT_ID)

aws apigateway create-resource \
  --rest-api-id $API_ID --parent-id $ROOT_ID --path-part "greet"

aws apigateway put-method \
  --rest-api-id $API_ID --resource-id $RESOURCE_ID \
  --http-method GET --authorization-type NONE

aws apigateway put-integration \
  --rest-api-id $API_ID --resource-id $RESOURCE_ID \
  --http-method GET --type AWS_PROXY --integration-http-method POST \
  --uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:greet-api/invocations
# Grant API Gateway permission to invoke the Lambda function
aws lambda add-permission \
  --function-name greet-api \
  --statement-id apigateway-invoke \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:us-east-1:123456789012:$API_ID/*/GET/greet"
resource "aws_lambda_function" "greet" {
  function_name = "greet-api"
  runtime       = "python3.12"
  handler       = "handler.lambda_handler"
  filename      = "function.zip"
  role          = aws_iam_role.lambda_exec.arn
}

resource "aws_apigatewayv2_api" "greet_api" {
  name          = "greet-api-gw"
  protocol_type = "HTTP"
}

resource "aws_apigatewayv2_integration" "lambda" {
  api_id                 = aws_apigatewayv2_api.greet_api.id
  integration_type       = "AWS_PROXY"
  integration_uri        = aws_lambda_function.greet.invoke_arn
  payload_format_version = "2.0"
}

resource "aws_apigatewayv2_route" "get_greet" {
  api_id    = aws_apigatewayv2_api.greet_api.id
  route_key = "GET /greet"
  target    = "integrations/${aws_apigatewayv2_integration.lambda.id}"
}

resource "aws_apigatewayv2_stage" "prod" {
  api_id      = aws_apigatewayv2_api.greet_api.id
  name        = "$default"
  auto_deploy = true
}

resource "aws_lambda_permission" "apigw" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.greet.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${aws_apigatewayv2_api.greet_api.execution_arn}/*/*"
}

HTTP APIs (aws_apigatewayv2_api) are cheaper and lower-latency than REST APIs for most use cases — use REST APIs only when you need features HTTP APIs lack (request validation, API keys/usage plans, WAF integration).

Request Flow Architecture

<svg viewBox="0 0 740 260" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:740px;font-family:sans-serif;color:currentColor">
  <style>
    .box { fill: none; stroke: currentColor; stroke-width: 1.5; }
    .lbl { fill: currentColor; font-size: 12px; }
    .accent { fill: currentColor; opacity: 0.12; }
    .arrow { stroke: currentColor; stroke-width: 1.3; marker-end: url(#a4); }
  </style>
  <defs>
    <marker id="a4" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto">
      <path d="M0,0 L8,3 L0,6 Z" fill="currentColor"/>
    </marker>
  </defs>
  <rect x="20" y="100" width="120" height="50" class="box accent"/>
  <text x="35" y="130" class="lbl">Client</text>

  <rect x="220" y="100" width="150" height="50" class="box"/>
  <text x="235" y="125" class="lbl">API Gateway</text>
  <text x="235" y="142" class="lbl" style="font-size:10px">auth, throttling, routing</text>

  <rect x="450" y="100" width="130" height="50" class="box accent"/>
  <text x="465" y="130" class="lbl">Lambda</text>

  <rect x="650" y="100" width="130" height="50" class="box" transform="translate(-60,0)"/>
  <text x="605" y="125" class="lbl">DynamoDB /</text>
  <text x="605" y="142" class="lbl">other services</text>

  <line x1="140" y1="125" x2="220" y2="125" class="arrow"/>
  <line x1="370" y1="125" x2="450" y2="125" class="arrow"/>
  <line x1="580" y1="125" x2="660" y2="125" class="arrow"/>
</svg>

Common Mistakes in Lambda + API Gateway Setups

  1. Overly broad IAM roles — attaching AdministratorAccess to a Lambda role instead of scoping permissions to exact resources/actions needed.
  2. No request validation — letting malformed requests reach Lambda wastes invocations; validate at the API Gateway layer with a JSON schema model.
  3. Ignoring cold starts on latency-critical paths — a checkout API in Python/Node with no provisioned concurrency can add 300ms+ tail latency under low traffic.
  4. Not setting a timeout matched to the integration — API Gateway has a hard 29-second timeout; a Lambda configured for 60s will still get cut off by the gateway.

FAQ

Is API Gateway required to use Lambda as an API? Not strictly — Lambda Function URLs offer a simpler direct HTTPS endpoint — but API Gateway adds throttling, custom domains, request validation, usage plans, and WAF integration that most production APIs need.

How do I reduce Lambda cold start latency? Use a lighter runtime (Node.js/Python over Java typically), keep deployment package size small, and apply provisioned concurrency to functions on latency-sensitive, low-traffic paths.

What's the difference between HTTP API and REST API in API Gateway? HTTP APIs (API Gateway v2) are cheaper and faster for simple proxy integrations; REST APIs (v1) support more features like request/response transformation, API keys, and usage plans — use REST only if you need those.

How do I secure a Lambda-backed API? Use an IAM authorizer, a Lambda authorizer (custom token logic), or a Cognito User Pool authorizer at the API Gateway layer — never rely on Lambda-internal checks alone, since that still exposes the invocation to unauthenticated traffic.

Build a Production-Ready Serverless API with F9 Infotech

Going from a working prototype to a production serverless API — proper IAM scoping, cold start mitigation, observability, and CI/CD — takes real AWS architecture experience. F9 Infotech's serverless engineering team can design, build, and operate your Lambda + API Gateway architecture end to end. Talk to our AWS serverless team about your API project.