Skip to main content

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)

HTTP routes

Create Your First API

Step 1: Create an HTTP API

  1. Go to API Gateway Console
  2. Click Create API
  3. Choose HTTP API (not REST API for now, simpler)
  4. Name it: ServerlessAPI
  5. Click Next

Step 2: Create an Integration

  1. Choose Create a new integration
  2. Select your Lambda function
  3. Click Create

Step 3: Define Routes

  1. Method: GET
  2. Resource path: /hello
  3. Integration: Your Lambda function
  4. Click Create

Step 4: Deploy

  1. Click Deploy (automatic stage creation)
  2. 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:

  1. Select your route
  2. Click CORS
  3. 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

CodeMeaningUse
200OKRequest succeeded
201CreatedResource created
400Bad RequestInvalid input
401UnauthorizedMissing credentials
403ForbiddenNo permission
404Not FoundResource doesn't exist
500Server ErrorSomething 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} → GetItem
  • POST /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

AspectCloud FunctionsCloud Run
PackageSource deploymentContainer image
HTTP HandlingAutomatic (request → function)Full web framework support
TimeoutSee GCP docsSee GCP docs
ScalingAutomaticAutomatic
ContainerBuilt-in runtimeCustom 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:

  1. Describe the vendor-neutral architecture (HTTP API, compute, data, observability).
  2. Map each component to AWS or GCP services.
  3. Explain why your choice of HTTP entry point fits your workload.

If you want feedback, email your write-up to maarifaarchitect@gmail.com.


References

Key Differences from AWS

AspectAWS API GatewayCloud Run
SetupSeparate serviceIntegral
FrameworkLambda-specificAny web framework
Cold Start100-500ms50-200ms (typically)
HTTP HandlingEvent-based (event.pathParameters)Standard request/response
ScalingAutoAuto (very fast)
PricingPer request + computePer request + compute

AWS API Gateway vs. Google Cloud Run

FactorAPI GatewayCloud Run
Ease of SetupRequires integration with LambdaSimpler (includes HTTP server)
Framework SupportLimited to Lambda modelsFull web framework support (Express, FastAPI, etc.)
CustomizationMore limitedVery flexible (write any web code)
EcosystemTight AWS integrationWorks with any container
CostSlightly cheaperSlightly more expensive
Cold StartSlowerFaster

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.