API Gateway is a fully managed API front-door. It handles request routing, auth, rate limiting, caching, and transformation — so your backend doesn't have to.
Real-World: Your mobile app calls https://api.example.com/orders. API Gateway receives it, validates the JWT token, rate-limits the user, transforms the request, forwards to Lambda, transforms the response, and returns it — all configured in JSON, no server maintained.
| Type | Protocol | Use Case |
|---|---|---|
| REST API | HTTP/HTTPS | Traditional APIs, full feature set |
| HTTP API | HTTP/HTTPS | Simpler, cheaper (70% cost reduction), less features |
| WebSocket API | WebSocket | Real-time bidirectional (chat, live data) |
| Feature | REST API | HTTP API |
|---|---|---|
| Usage plans + API keys | Yes | No |
| Request/response transformation | Yes | No |
| AWS WAF integration | Yes | No |
| Resource policies | Yes | No |
| X-Ray tracing | Yes | No |
| Caching | Yes | No |
| Cost | Higher | 71% cheaper |
| Lambda Proxy | Yes | Yes |
| JWT Authorizer | No | Yes (native) |
| OIDC/OAuth2 | No | Yes (native) |
Rule of thumb: Use HTTP API for simple Lambda/HTTP backends. Use REST API when you need caching, WAF, usage plans, or complex transformations.
API Gateway passes the entire request to Lambda as an event. Lambda must return the full HTTP response format.
// Lambda receives this event
{
"httpMethod": "POST",
"path": "/orders",
"headers": {"Authorization": "Bearer eyJ..."},
"pathParameters": {"orderId": "123"},
"queryStringParameters": {"status": "pending"},
"body": "{\"item\": \"laptop\"}",
"requestContext": {...}
}# Lambda must return this exact structure
def handler(event, context):
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'orderId': '123', 'status': 'created'})
}API Gateway calls AWS services directly (no Lambda needed):
POST /orders → API Gateway → DynamoDB PutItem (directly)
Real-World: Simple data submission form → API Gateway → DynamoDB. No Lambda needed, reduces cost and latency.
Forward to existing HTTP backend (on-premise or other AWS services):
GET /products → API Gateway → ECS container service
Uses AWS SigV4. Best for service-to-service communication within AWS.
AWS SDK (signs request) → API Gateway (validates SigV4) → Lambda
Validates JWT from Cognito. Built-in, no Lambda needed.
Mobile App → Cognito (login) → JWT token
→ API Gateway (validates JWT against Cognito) → Lambda
Lambda validates your custom token and returns an IAM policy.
Request with Bearer token → API Gateway → Lambda Authorizer →
IAM Policy {Allow/Deny} → API Gateway caches policy for N seconds →
Routes to backend
def authorizer_handler(event, context):
token = event['authorizationToken']
# Validate token with your auth server
user = validate_token(token)
if user:
return generate_policy(user['id'], 'Allow', event['methodArn'])
else:
return generate_policy('unknown', 'Deny', event['methodArn'])
def generate_policy(principal_id, effect, resource):
return {
'principalId': principal_id,
'policyDocument': {
'Version': '2012-10-17',
'Statement': [{
'Action': 'execute-api:Invoke',
'Effect': effect,
'Resource': resource
}]
},
'context': { # Passed to Lambda backend
'userId': principal_id
}
}Lambda Authorizer caching: Set TTL (0-3600s). Cache key = authorization token. Reduces Lambda invocations and latency.
API Gateway API
├── Stage: dev → Lambda alias: dev
├── Stage: staging → Lambda alias: staging
└── Stage: prod → Lambda alias: prod
Stage Variables: Like environment variables for stages.
Stage: prod
stageVariable: lambdaAlias = prod
Integration URI: arn:aws:lambda:...:function:myFunc:${stageVariables.lambdaAlias}
Changing lambdaAlias in stage variables changes which Lambda version is called — no redeployment of API needed.
API Gateway supports canary releases without needing CodeDeploy:
prod stage: 90% traffic → current deployment
10% traffic → canary deployment
Test new API config with 10% of traffic, then promote or rollback.
REST API only. Cache responses to reduce Lambda invocations.
Client → API Gateway (cache hit? return cached) → Lambda (only on cache miss)
- Cache size: 0.5GB – 237GB
- TTL: 0 – 3600 seconds (default 300)
- Cache key: method + path + optional query params/headers
Real-World: Product catalog API — same product details called millions of times. Cache for 5 minutes = 99%+ cache hit rate = 99% fewer Lambda calls.
Invalidation: Client sends Cache-Control: max-age=0 header (needs IAM permission execute-api:InvalidateCache).
- Default: 10,000 requests/second per account (soft limit)
- Burst: 5,000 requests (Token Bucket algorithm)
Control per API key:
Usage Plan: free-tier
- Throttle: 10 req/sec
- Quota: 1,000 req/day
Usage Plan: premium
- Throttle: 100 req/sec
- Quota: 100,000 req/day
Client → API Gateway → check API key → lookup usage plan → enforce limits
Real-World: SaaS product with free/paid tiers. Free users get 1,000 API calls/day. Paid users get unlimited.
Mapping Templates (VTL — Apache Velocity) transform requests before hitting backend and responses before returning to client.
Use Case: API accepts one format, backend expects another.
## Transform frontend JSON to DynamoDB PutItem format
#set($inputRoot = $input.path('$'))
{
"TableName": "orders",
"Item": {
"orderId": {"S": "$inputRoot.id"},
"userId": {"S": "$inputRoot.user"},
"total": {"N": "$inputRoot.total"}
}
}Persistent bidirectional connections. API Gateway manages connection state.
Client connects → connectionId assigned
Client sends message → route based on routeKey
Server sends message → API Gateway Management API
Client disconnects
Route selection: Based on JSON body field (e.g., action).
{"action": "sendMessage", "data": "Hello"}
→ routes to $connect, $disconnect, sendMessage, $defaultSending to connected clients from Lambda:
apigw_client = boto3.client('apigatewaymanagementapi',
endpoint_url=f"https://{domain}/{stage}")
apigw_client.post_to_connection(
ConnectionId=connection_id,
Data=json.dumps({"message": "Hello from server"})
)Real-World: Collaborative editing, live chat, real-time dashboards, multiplayer gaming.
For browser-based clients:
{
"allowOrigins": ["https://app.example.com"],
"allowMethods": ["GET", "POST", "PUT", "DELETE"],
"allowHeaders": ["Authorization", "Content-Type"],
"maxAge": 3600
}API Gateway handles OPTIONS preflight requests automatically when CORS is configured.
| Practice | Reason |
|---|---|
| Use Lambda Proxy integration | Simpler, full request/response control |
| Cache with appropriate TTL | Reduce Lambda invocations and cost |
| Use usage plans with API keys | Rate limiting per client |
| Use Cognito authorizer for user auth | Managed, no custom Lambda needed |
| Use Lambda Authorizer for custom auth | Token from non-AWS providers |
| Enable X-Ray tracing | End-to-end tracing across API → Lambda |
| Use stages with stage variables | Avoid hardcoding Lambda ARN in integration |
| Set 4xx/5xx CloudWatch alarms | Detect errors before users complain |
| Anti-Pattern | Impact | Fix |
|---|---|---|
| No throttling | One bad client takes down your API | Enable usage plans + throttling |
| Caching sensitive/user-specific data | User A sees User B's data | Don't cache user-specific responses; or include user ID in cache key |
| Lambda Authorizer with TTL=0 | Auth Lambda called every request = latency + cost | Set reasonable TTL (300s) |
| Exposing internal error messages | Information leakage | Use Gateway Response to customize 4xx/5xx |
| Using REST API when HTTP API is enough | 3x cost for no benefit | Start with HTTP API |
- 29-second timeout — hard limit. If Lambda takes longer, API Gateway returns 504 (Gateway Timeout). Can't be increased.
- 10MB payload limit — max request/response body size.
- HTTP API is NOT the same as REST API — HTTP API lacks WAF, caching, usage plans.
- Lambda Proxy vs Lambda Custom: Proxy = entire HTTP request to Lambda. Custom = mapping template transforms.
- Edge-Optimized vs Regional vs Private:
- Edge-Optimized: CloudFront in front, global users
- Regional: deployed in specific region, same-region clients
- Private: only accessible within VPC
- Stages need deployment — changes to API don't go live until you deploy to a stage.
- CORS must be enabled on API Gateway AND Lambda must return CORS headers (for Lambda Proxy).
- API Keys alone are NOT security — they're for throttling/monitoring, not auth. Use + IAM/Cognito/Lambda Authorizer for real auth.
Q: API returns 504 error occasionally under heavy load? → Lambda timeout exceeded. Optimize Lambda or return response asynchronously (SQS pattern).
Q: How to build a real-time chat with API Gateway? → Use WebSocket API + Lambda + DynamoDB (store connection IDs).
Q: Different clients need different rate limits? → Usage Plans + API Keys.
Q: Third-party OAuth2 provider for auth? → Lambda Authorizer or HTTP API's native JWT Authorizer.
Q: Reduce costs on a read-heavy product catalog API? → Enable API Gateway caching with appropriate TTL.
Q: Call DynamoDB directly from API Gateway without Lambda? → Use AWS Service Integration with Mapping Templates.