API Gateway & Lambda: Build Serverless APIs
API Gateway is an AWS managed service that creates HTTP endpoints and routes requests to Lambda functions. Combined, they enable you to build fully serverless REST APIs: API Gateway handles routing, TLS termination, and rate limiting; Lambda executes your business logic. You pay per million requests to API Gateway and per Lambda invocation, with no servers to manage.
What is API Gateway and How Does It Integrate with Lambda?
API Gateway acts as a "front door" for your application. It receives HTTP requests on a public URL, applies security rules (authentication, CORS, throttling), and forwards the request to a Lambda function. Lambda processes the request and returns a response; API Gateway formats it as HTTP and sends it back to the client.
The integration is defined by a Proxy Lambda integration: API Gateway passes the entire HTTP request (method, path, headers, body) as a single event dictionary to Lambda, and expects Lambda to return a properly formatted HTTP response (status code, headers, body).
Create an API Gateway REST API
In the AWS Console:
- Go to API Gateway → Create API
- Choose REST API (not HTTP API, which is simpler but less flexible)
- Click Build
- Choose New API and enter name:
my-api - Click Create API
You now have a REST API with a root resource /. Next, create a Lambda function to back it.
Create a Lambda Function for API Handling
In the Lambda Console, create a new function or modify an existing one:
import json
def lambda_handler(event, context):
# Extract request details
method = event['httpMethod']
path = event['path']
# Route based on path
if path == '/hello':
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'message': 'Hello, World!'})
}
elif path == '/users' and method == 'GET':
# Mock user list
users = [
{'id': 1, 'name': 'Alice'},
{'id': 2, 'name': 'Bob'}
]
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(users)
}
else:
return {
'statusCode': 404,
'body': json.dumps({'error': 'Not found'})
}
Connect API Gateway to Lambda
In the API Gateway Console:
- Select the / (root) resource
- Click Actions → Create Method → ANY
- Choose Lambda Function
- Enable Lambda Proxy integration (critical—this passes the full HTTP request)
- Enter your function name (e.g.,
my-function) - Click Save → OK when prompted for permission
Now requests to ANY / are routed to your Lambda function.
Deploy the API
- Click Actions → Deploy API
- Choose a deployment stage (or create new:
dev) - Click Deploy
AWS creates a public HTTPS endpoint: https://abc123.execute-api.us-east-1.amazonaws.com/dev/
Test it:
curl https://abc123.execute-api.us-east-1.amazonaws.com/dev/hello
# Output: {"message": "Hello, World!"}
curl https://abc123.execute-api.us-east-1.amazonaws.com/dev/users
# Output: [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
Handling Path Parameters
Create sub-resources in API Gateway to handle dynamic paths. For a /users/{id} endpoint:
- Select the / resource
- Click Actions → Create Resource
- Enter name:
users - Click Create Resource
- Select the new
/usersresource - Click Actions → Create Resource
- Enter name:
{id}(curly braces indicate a path parameter) - Click Create Resource
- Select
/{id}and create a GET method - Configure as before: Lambda Proxy integration
In your Lambda handler:
def lambda_handler(event, context):
path_params = event.get('pathParameters', {})
user_id = path_params.get('id') if path_params else None
if user_id:
# Fetch user by ID
user = {'id': user_id, 'name': f'User {user_id}', 'email': f'user{user_id}@example.com'}
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(user)
}
return {'statusCode': 400, 'body': json.dumps({'error': 'ID required'})}
Test:
curl https://abc123.execute-api.us-east-1.amazonaws.com/dev/users/123
# Output: {"id": "123", "name": "User 123", "email": "[email protected]"}
Handling Query Strings and Request Body
API Gateway includes query strings and body data in the event:
import json
def lambda_handler(event, context):
method = event['httpMethod']
# Query string parameters
query_params = event.get('queryStringParameters', {}) or {}
# Request body
body = event.get('body')
if body:
if event.get('isBase64Encoded'):
body = json.loads(body)
else:
body = json.loads(body)
if method == 'GET':
# /search?q=python&limit=10
query = query_params.get('q', 'no query')
limit = query_params.get('limit', '10')
return {
'statusCode': 200,
'body': json.dumps({'query': query, 'limit': limit})
}
elif method == 'POST':
# POST with JSON body
try:
data = json.loads(body) if isinstance(body, str) else body
name = data.get('name', 'unknown')
return {
'statusCode': 201,
'body': json.dumps({'created': True, 'name': name})
}
except Exception as e:
return {
'statusCode': 400,
'body': json.dumps({'error': str(e)})
}
return {'statusCode': 405, 'body': json.dumps({'error': 'Method not allowed'})}
Test POST:
curl -X POST https://abc123.execute-api.us-east-1.amazonaws.com/dev/users \
-H "Content-Type: application/json" \
-d '{"name": "Charlie"}'
# Output: {"created": true, "name": "Charlie"}
Enable CORS (Cross-Origin Requests)
By default, browsers block cross-origin requests. Enable CORS in API Gateway:
- Select a resource (e.g.,
/users) - Click Actions → Enable CORS and replace existing CORS headers
- Review and click Enable CORS and replace existing CORS headers
API Gateway automatically adds a OPTIONS method to handle CORS preflight requests.
Alternatively, set CORS headers in your Lambda response:
def lambda_handler(event, context):
headers = {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type'
}
return {
'statusCode': 200,
'headers': headers,
'body': json.dumps({'message': 'CORS enabled'})
}
Request/Response Mapping and Validation
For advanced scenarios, use request validators to enforce schema:
- Select API → Request Validators
- Click Create New Request Validator
- Check Validate request body
- Select a method and click Enable
- Define a JSON Schema model in Models
This ensures only valid requests reach your Lambda function, reducing error handling overhead.
Performance Considerations
API Gateway Proxy Integration introduces minimal latency (~10–50 ms) but can be optimized:
- Enable API caching: Cache GET responses for 5–60 seconds (ideal for read-heavy APIs).
- Use CloudFront: Place CloudFront in front for edge caching and DDoS protection.
- Optimize Lambda cold starts: See the cold-starts article for strategies like provisioned concurrency and code optimization.
Key Takeaways
- API Gateway creates public HTTPS endpoints and routes requests to Lambda functions using Proxy Integration.
- Enable Lambda Proxy Integration to receive the full HTTP request (method, path, headers, body, query parameters) as an event dictionary.
- Lambda must return a response object with
statusCode,headers, andbodykeys. - Path parameters use
{paramName}syntax; access viaevent['pathParameters']. - Query strings and body data are in
event['queryStringParameters']andevent['body']respectively. - Enable CORS via API Gateway console or manually add headers in Lambda responses.
- Deploy to a stage to generate a public HTTPS URL; re-deploy after code changes.
Frequently Asked Questions
What's the difference between REST API and HTTP API?
REST API offers more features: resource-based routing, request validators, API keys, usage plans, and fine-grained logging. HTTP API is simpler and cheaper (60% less cost) but lacks some advanced features. Use HTTP API for simple APIs; REST API for complex routing and security.
How do I secure my API?
Use API Keys (require callers to include an X-API-Key header), AWS IAM authentication (for service-to-service), or Lambda authorizers (custom authorization logic). For user authentication, integrate with Cognito. Store secrets in AWS Secrets Manager, not in code.
Can I version my API?
Yes. Use stages (/dev, /prod) or include version in the path (/v1/users, /v2/users). Each stage can point to different Lambda function versions or code, enabling independent deployments.
How much does API Gateway cost?
$3.50 per million requests, plus data transfer charges. Free tier: 1 million requests/month (first 12 months). Typical small API: $5–50/month depending on traffic.
Can I use Lambda with HTTP API instead of REST API?
Yes. HTTP API is 70% cheaper than REST API and lower latency, but has fewer features. Good for simple microservices; REST API for complex routing and advanced security.
Further Reading
- API Gateway Documentation — Complete API Gateway reference
- Lambda Proxy Integration — Event and response format specification
- Securing APIs with Cognito — User authentication patterns
- API Gateway Best Practices — Design and performance recommendations