Skip to main content

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:

  1. Go to API GatewayCreate API
  2. Choose REST API (not HTTP API, which is simpler but less flexible)
  3. Click Build
  4. Choose New API and enter name: my-api
  5. 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:

  1. Select the / (root) resource
  2. Click ActionsCreate MethodANY
  3. Choose Lambda Function
  4. Enable Lambda Proxy integration (critical—this passes the full HTTP request)
  5. Enter your function name (e.g., my-function)
  6. Click SaveOK when prompted for permission

Now requests to ANY / are routed to your Lambda function.

Deploy the API

  1. Click ActionsDeploy API
  2. Choose a deployment stage (or create new: dev)
  3. 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:

  1. Select the / resource
  2. Click ActionsCreate Resource
  3. Enter name: users
  4. Click Create Resource
  5. Select the new /users resource
  6. Click ActionsCreate Resource
  7. Enter name: {id} (curly braces indicate a path parameter)
  8. Click Create Resource
  9. Select /{id} and create a GET method
  10. 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:

  1. Select a resource (e.g., /users)
  2. Click ActionsEnable CORS and replace existing CORS headers
  3. 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:

  1. Select APIRequest Validators
  2. Click Create New Request Validator
  3. Check Validate request body
  4. Select a method and click Enable
  5. 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, and body keys.
  • Path parameters use {paramName} syntax; access via event['pathParameters'].
  • Query strings and body data are in event['queryStringParameters'] and event['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