Skip to content

API Gateway

First PublishedByAtif Alam

API Gateway is a fully managed service that creates, publishes, and manages APIs at any scale. It’s the front door for serverless applications — sitting between clients and your Lambda functions, HTTP backends, or AWS services.

TypeProtocolBest ForCost
HTTP APIHTTPSimple Lambda/HTTP proxy, low latency, low cost~$1.00 per million requests
REST APIHTTPFull feature set (usage plans, API keys, request validation, caching)~$3.50 per million requests
WebSocket APIWebSocketReal-time bidirectional communication (chat, live updates)Per message + connection minutes

HTTP API is the recommended default — it’s cheaper, faster, and covers most use cases. Use REST API only when you need features like request validation, caching, or usage plans.

Client ──HTTPS──► API Gateway ──► Integration (Lambda, HTTP, AWS service)
├── Authentication (IAM, Cognito, JWT, API key)
├── Request validation
├── Throttling / rate limiting
├── Caching (REST API only)
└── Logging (CloudWatch)
lambda_function.py
import json
def handler(event, context):
method = event['requestContext']['http']['method']
path = event['requestContext']['http']['path']
if method == 'GET' and path == '/users':
return {
'statusCode': 200,
'body': json.dumps([
{'id': 1, 'name': 'Alice'},
{'id': 2, 'name': 'Bob'}
])
}
return {'statusCode': 404, 'body': json.dumps({'error': 'Not found'})}
Terminal window
# Create HTTP API with Lambda integration
aws apigatewayv2 create-api \
--name my-api \
--protocol-type HTTP \
--target arn:aws:lambda:us-east-1:123456789012:function:my-function
# The output includes the API endpoint:
# https://abc123.execute-api.us-east-1.amazonaws.com

Routes map HTTP methods and paths to integrations:

Terminal window
# Create a route
aws apigatewayv2 create-route \
--api-id abc123 \
--route-key "GET /users"
# Route examples:
# GET /users → list users
# POST /users → create user
# GET /users/{id} → get user by ID
# DELETE /users/{id} → delete user
# ANY /{proxy+} → catch-all (proxy everything to Lambda)

The {proxy+} greedy path parameter forwards all requests to a single Lambda, which handles routing internally (common with frameworks like Express, FastAPI, Flask).

Stages represent different environments for your API:

https://abc123.execute-api.us-east-1.amazonaws.com/dev → dev stage
https://abc123.execute-api.us-east-1.amazonaws.com/staging → staging stage
https://abc123.execute-api.us-east-1.amazonaws.com/prod → production stage

Each stage can have:

  • Stage variables — Key-value pairs (like environment variables for the API).
  • Different Lambda aliasesdev stage → $LATEST, prod stage → production alias.
  • Different throttling limits — Lower limits for dev, higher for prod.

HTTP APIs have an $default stage that serves at the root URL (no stage in the path).

Requests must be signed with AWS credentials (SigV4). Best for service-to-service calls within AWS.

Validate JWTs from Cognito, Auth0, or any OIDC provider:

Terminal window
aws apigatewayv2 create-authorizer \
--api-id abc123 \
--authorizer-type JWT \
--name cognito-auth \
--identity-source '$request.header.Authorization' \
--jwt-configuration '{
"Audience": ["my-app-client-id"],
"Issuer": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc123"
}'

A custom Lambda function that validates tokens and returns an IAM policy:

authorizer.py
def handler(event, context):
token = event['headers'].get('authorization', '')
if validate_token(token):
return {
'isAuthorized': True,
'context': {'userId': 'user_123', 'role': 'admin'}
}
return {'isAuthorized': False}

The context object is passed to the backend Lambda — useful for sharing user info without re-validating.

API keys identify callers; usage plans set rate limits per key:

Terminal window
# Create a usage plan
aws apigateway create-usage-plan --name "Basic" \
--throttle burstLimit=100,rateLimit=50 \
--quota limit=10000,period=MONTH
# Create an API key
aws apigateway create-api-key --name "partner-a" --enabled
# Associate key with usage plan
aws apigateway create-usage-plan-key \
--usage-plan-id abc123 --key-id xyz789 --key-type API_KEY

Clients pass the key in the x-api-key header. Note: API keys are for throttling and tracking, not authentication — they’re not a security mechanism.

API Gateway has built-in throttling to protect backends:

LevelWhat It Controls
Account level10,000 requests/second across all APIs (soft limit)
Stage levelPer-stage throttle (e.g. 5,000 rps for prod)
Route levelPer-route throttle (e.g. 100 rps for /expensive-operation)
Usage planPer-API-key throttle

When throttled, clients receive a 429 Too Many Requests response.

Validate request bodies and parameters before they reach your backend:

{
"type": "object",
"required": ["name", "email"],
"properties": {
"name": {"type": "string", "minLength": 1},
"email": {"type": "string", "format": "email"}
}
}

Invalid requests get a 400 Bad Request — your Lambda never runs.

Map Lambda output to HTTP responses:

Lambda returns: {"statusCode": 200, "body": "...", "headers": {...}}
API Gateway: HTTP 200 with body and headers

For HTTP APIs, Lambda must return the statusCode/body/headers structure. API Gateway passes it through directly.

Cross-Origin Resource Sharing configuration for browser-based clients:

Terminal window
# HTTP API — configure CORS
aws apigatewayv2 update-api --api-id abc123 \
--cors-configuration '{
"AllowOrigins": ["https://myapp.com"],
"AllowMethods": ["GET", "POST", "PUT", "DELETE"],
"AllowHeaders": ["Authorization", "Content-Type"],
"MaxAge": 86400
}'

HTTP APIs handle CORS automatically. REST APIs require manual OPTIONS method setup or enabling CORS in the console.

Map your API to a custom domain instead of the auto-generated URL:

api.example.com → API Gateway → Lambda
Terminal window
# Create custom domain (needs an ACM certificate)
aws apigatewayv2 create-domain-name \
--domain-name api.example.com \
--domain-name-configurations CertificateArn=arn:aws:acm:...:cert
# Map to API stage
aws apigatewayv2 create-api-mapping \
--domain-name api.example.com \
--api-id abc123 \
--stage '$default'

Then create a Route 53 alias record pointing api.example.com to the API Gateway domain.

REST APIs can cache responses to reduce Lambda invocations and latency:

SettingWhat It Does
Cache capacity0.5 GB – 237 GB
TTL0 – 3600 seconds (default 300)
Cache keyBy path, query strings, headers
Per-methodEnable/disable per method

Cache is charged per hour by capacity size.

SignalWhere
MetricsCloudWatch: Count, Latency, 4XXError, 5XXError, IntegrationLatency
Access logsCloudWatch Logs or Kinesis Firehose (request/response details)
Execution logsCloudWatch Logs (detailed debug — method request/response, integration)
X-Ray tracingEnd-to-end trace through API Gateway → Lambda → downstream
Client ──► API Gateway (HTTP API) ──► Lambda ──► DynamoDB
└── JWT authorizer (Cognito)
Client ──► API Gateway ──► /users/* ──► User service (ALB)
──► /orders/* ──► Orders Lambda
──► /payments/* ──► Payment service (NLB)
Client ◄──WebSocket──► API Gateway ──► Lambda ($connect, $disconnect, $default)
DynamoDB (connection table)
  • HTTP API is cheaper and faster — use it by default. Use REST API only for caching, request validation, or usage plans.
  • API Gateway handles authentication (JWT, IAM, Lambda authorizer), throttling, and CORS so your backend doesn’t have to.
  • Use {proxy+} catch-all routes with a framework (FastAPI, Express) for simpler routing inside Lambda.
  • Stages separate dev/staging/prod with different configs and throttle limits.
  • API keys are for tracking and throttling, not security — use JWT or IAM for authentication.
  • Enable CloudWatch access logs and X-Ray for observability.