Building REST APIs: API Gateway (AWS) & Cloud Run (GCP)
HTTP APIs for Serverless Functions
Both AWS and GCP provide ways to expose serverless functions as REST APIs:
- AWS: API Gateway + Lambda
- GCP: Cloud Run with HTTP triggers
Simple Explanation
What it is
This lesson shows how to put an HTTP front door in front of your serverless code so browsers, mobile apps, and other services can call it.
Why we need it
Most real products talk over HTTP. Without an API layer, your functions cannot be reached by clients in a clean, secure way.
Benefits
- Standard interface that works with any client.
- Scales automatically alongside your functions.
- Clear separation between routing and business logic.
Tradeoffs
- Extra layer to configure and secure.
- More moving parts for debugging and tracing.
Real-world examples (architecture only)
- Mobile app → API Gateway → Function → Database.
- Partner integration → HTTP endpoint → Function → Queue.
HTTP Basics (Both Clouds)
Create Your First API
Step 1: Create an HTTP API
- Go to API Gateway Console
- Click Create API
- Choose HTTP API (not REST API for now, simpler)
- Name it:
ServerlessAPI - Click Next
Step 2: Create an Integration
- Choose Create a new integration
- Select your Lambda function
- Click Create
Step 3: Define Routes
- Method:
GET - Resource path:
/hello - Integration: Your Lambda function
- Click Create
Step 4: Deploy
- Click Deploy (automatic stage creation)
- Copy the Invoke URL
Step 5: Test
curl https://abc123.execute-api.us-east-1.amazonaws.com/hello
You'll get your Lambda's response!
API Gateway + Lambda Flow
HTTP Request
↓
API Gateway (maps /hello to Lambda)
↓
Lambda invoked with event: { path: '/hello', method: 'GET', ... }
↓
Your code returns response
↓
API Gateway (formats as HTTP response)
↓
HTTP Response (200, body, headers)
Event Structure in Lambda
import json
def handler(event, context):
print(event)
# event includes path, headers, requestContext, and body
return {
"statusCode": 200,
"body": json.dumps({"message": "Hello from API!"}),
"headers": {"Content-Type": "application/json"},
}
What this does: Logs the incoming event and returns a JSON response that API Gateway turns into an HTTP response.
Handling Path Parameters
Resource: /items/{id}
Request: GET /items/123
Lambda receives:
# event["pathParameters"] = {"id": "123"}
Your code:
import json
def handler(event, context):
item_id = event.get("pathParameters", {}).get("id")
return {
"statusCode": 200,
"body": json.dumps({"itemId": item_id}),
}
Handling Query Strings
Request: GET /search?q=serverless&limit=10
Lambda receives:
# event["queryStringParameters"] = {"q": "serverless", "limit": "10"}
Handling Request Body (POST/PUT)
import json
def handler(event, context):
body = json.loads(event.get("body") or "{}")
# body = {"name": "My Item", "description": "..."}
return {
"statusCode": 201,
"body": json.dumps({"id": "new-id", **body}),
}
CORS (Cross-Origin Requests)
If your frontend is on a different domain, enable CORS:
In API Gateway Console:
- Select your route
- Click CORS
- Check Automatic CORS configuration
Or in Lambda:
return {
"statusCode": 200,
"headers": {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json",
},
"body": json.dumps({"message": "Hello"}),
}
Error Handling
def handler(event, context):
try:
return {"statusCode": 200, "body": "..."}
except Exception as exc:
print(exc)
return {
"statusCode": 500,
"body": json.dumps({"error": "Internal server error"}),
}
Status Codes
| Code | Meaning | Use |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created |
| 400 | Bad Request | Invalid input |
| 401 | Unauthorized | Missing credentials |
| 403 | Forbidden | No permission |
| 404 | Not Found | Resource doesn't exist |
| 500 | Server Error | Something broke |
Hands-On: Build a Simple API
1. Create two Lambda functions:
GetItem:
import json
def handler(event, context):
item_id = event.get("pathParameters", {}).get("id")
return {
"statusCode": 200,
"body": json.dumps({"id": item_id, "name": f"Item {item_id}"}),
}
CreateItem:
import json
def handler(event, context):
item = json.loads(event.get("body") or "{}")
return {
"statusCode": 201,
"body": json.dumps({"id": "new-id", **item}),
}
2. Create HTTP API
3. Add routes:
GET /items/{id}→ GetItemPOST /items→ CreateItem
4. Test:
curl https://your-api.execute-api.region.amazonaws.com/items/123
curl -X POST https://... -d '{"name":"My Item"}'
Part 2: Google Cloud Run
What Is Cloud Run?
Cloud Run directly exposes containerized functions (or web apps) as HTTP endpoints. No separate API Gateway needed.
Cloud Run vs. Cloud Functions
| Aspect | Cloud Functions | Cloud Run |
|---|---|---|
| Package | Source deployment | Container image |
| HTTP Handling | Automatic (request → function) | Full web framework support |
| Timeout | See GCP docs | See GCP docs |
| Scaling | Automatic | Automatic |
| Container | Built-in runtime | Custom Docker |
Building a Simple HTTP Service
# app.py - Flask app on Cloud Run
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.get("/items/<item_id>")
def get_item(item_id):
return jsonify({"id": item_id, "name": f"Item {item_id}"}), 200
@app.post("/items")
def create_item():
item = request.get_json(silent=True) or {}
return jsonify({"id": "new-id", **item}), 201
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "app.py"]
Deploy to Cloud Run
# Build and push container
gcloud run deploy my-api \
--source . \
--platform managed \
--region us-central1 \
--allow-unauthenticated
You get a URL immediately:
Service [my-api] revision [my-api-v1] has been deployed.
Service URL: https://my-api-abc123.a.run.app
Test:
curl https://my-api-abc123.a.run.app/items/123
curl -X POST https://my-api-abc123.a.run.app/items \
-H "Content-Type: application/json" \
-d '{"name":"My Item"}'
Project (Cloud-Agnostic)
Build a simple REST API with two endpoints: list items and create item.
Deliverables:
- Describe the vendor-neutral architecture (HTTP API, compute, data, observability).
- Map each component to AWS or GCP services.
- Explain why your choice of HTTP entry point fits your workload.
If you want feedback, email your write-up to [email protected].
References
- AWS API Gateway: https://docs.aws.amazon.com/apigateway/
- AWS Lambda Developer Guide: https://docs.aws.amazon.com/lambda/latest/dg/welcome.html
- Google Cloud Run: https://cloud.google.com/run/docs
- Google Cloud Functions: https://docs.cloud.google.com/functions/docs
- Google Cloud API Gateway: https://cloud.google.com/api-gateway/docs
Key Differences from AWS
| Aspect | AWS API Gateway | Cloud Run |
|---|---|---|
| Setup | Separate service | Integral |
| Framework | Lambda-specific | Any web framework |
| Cold Start | 100-500ms | 50-200ms (typically) |
| HTTP Handling | Event-based (event.pathParameters) | Standard request/response |
| Scaling | Auto | Auto (very fast) |
| Pricing | Per request + compute | Per request + compute |
AWS API Gateway vs. Google Cloud Run
| Factor | API Gateway | Cloud Run |
|---|---|---|
| Ease of Setup | Requires integration with Lambda | Simpler (includes HTTP server) |
| Framework Support | Limited to Lambda models | Full web framework support (Express, FastAPI, etc.) |
| Customization | More limited | Very flexible (write any web code) |
| Ecosystem | Tight AWS integration | Works with any container |
| Cost | Slightly cheaper | Slightly more expensive |
| Cold Start | Slower | Faster |
Which Should You Use?
Use API Gateway if:
- You're committing to AWS Lambda
- You want tight integration with AWS services
- You prefer simple function-based APIs
Use Cloud Run if:
- You want Docker flexibility
- You need web framework features (middleware, templating)
- You want faster cold starts
- You may migrate containers to Kubernetes later
Best Practice: Learn both. They're both solid serverless API platforms.
Key Takeaway
API Gateway transforms your Lambda functions into production-ready REST APIs. No web server, no port management. Just code and routing.