Aws
AwsIntermediate

AWS API Gateway Patterns: Building Scalable APIs

DeveloperHat Team
8 min read
API GatewayRESTServerlessIntegration

TL;DR

Learn essential patterns and best practices for designing and implementing APIs using Amazon API Gateway, including security, integration patterns, and performance optimization

Amazon API Gateway enables you to create, publish, maintain, monitor, and secure APIs at any scale. But choosing the right API Gateway configuration involves critical trade-offs that impact your application's performance, cost, and operational complexity. This guide provides a decision framework for building robust APIs.

$1

API Gateway is often the front door to your entire backend infrastructure. A poorly designed API layer can become a bottleneck that limits your ability to scale, increases costs exponentially, and creates security vulnerabilities. Conversely, a well-architected API Gateway setup enables you to:

  • Scale independently from your backend services
  • Reduce Lambda cold starts through proper caching strategies
  • Cut costs by 40-70% with HTTP APIs vs REST APIs for simple use cases
  • Protect backend services from traffic spikes and DDoS attacks
  • The decisions you make here cascade through your entire system architecture.

    ``mermaid

    %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#FF9900', 'primaryTextColor': '#232F3E', 'primaryBorderColor': '#232F3E', 'lineColor': '#232F3E', 'secondaryColor': '#147EB4', 'tertiaryColor': '#232F3E', 'fontFamily': 'system-ui', 'fontSize': '14px' }}}%%

    graph TB

    subgraph Frontend["Client Applications"]

    direction TB

    Web["Web"]

    Mobile["Mobile"]

    IoT["IoT"]

    end

    subgraph Gateway["API Gateway"]

    direction TB

    subgraph Endpoints["API Endpoints"]

    direction LR

    REST["REST"]

    HTTP["HTTP"]

    WebSocket["WebSocket"]

    end

    subgraph Features["Gateway Features"]

    direction LR

    Auth["Authentication"]

    Cache["Caching"]

    Throttle["Throttling"]

    end

    subgraph Integration["Integration Types"]

    direction LR

    Lambda["Lambda"]

    HTTP_INT["HTTP"]

    Mock["Mock"]

    end

    end

    subgraph Backend["Backend Services"]

    direction TB

    subgraph Compute["Compute"]

    direction LR

    LambdaFn["Lambda"]

    ECS["ECS"]

    EC2["EC2"]

    end

    subgraph Data["Data Services"]

    direction LR

    DynamoDB["DynamoDB"]

    RDS["RDS"]

    S3["S3"]

    end

    subgraph Messaging["Event Services"]

    direction LR

    SNS["SNS"]

    SQS["SQS"]

    EventBridge["EventBridge"]

    end

    end

    Frontend --> Gateway

    Gateway --> Backend

    classDef frontendNode fill:#FF9900,stroke:#232F3E,color:#232F3E,stroke-width:2px,font-weight:bold

    classDef gatewayNode fill:#232F3E,stroke:#232F3E,color:#FFFFFF,stroke-width:2px,font-weight:bold

    classDef backendNode fill:#147EB4,stroke:#232F3E,color:#FFFFFF,stroke-width:2px,font-weight:bold

    classDef groupStyle fill:transparent,stroke:#232F3E,stroke-width:2px,color:#232F3E,font-weight:bold

    class Web,Mobile,IoT frontendNode

    class REST,HTTP,WebSocket,Auth,Cache,Throttle,Lambda,HTTP_INT,Mock gatewayNode

    class LambdaFn,ECS,EC2,DynamoDB,RDS,S3,SNS,SQS,EventBridge backendNode

    class Frontend,Gateway,Backend,Endpoints,Features,Integration,Compute,Data,Messaging groupStyle

    `

    $1

    This is the first and most impactful decision you'll make. AWS offers two API types with dramatically different capabilities and costs.

    Choose HTTP API when:

  • You need Lambda or HTTP backend integrations only
  • JWT authentication (Cognito, Auth0, Okta) is sufficient
  • You want to minimize costs (up to 71% cheaper)
  • Low latency is critical (HTTP APIs have lower overhead)
  • You don't need API Gateway features like request/response transformation, API keys, or usage plans
  • Real-world example: A mobile app backend with Cognito authentication and Lambda functions. HTTP API is the clear winner here, saving thousands of dollars monthly at scale.

    $1

    Choose REST API when:

  • You need AWS WAF integration for security
  • API key management and usage plans are required (B2B APIs)
  • You need request/response transformation (mapping templates)
  • Edge-optimized endpoints with CloudFront integration
  • You need caching at the API Gateway level
  • VPC Link with private ALB/NLB is required
  • Real-world example: A B2B API where partners need API keys, rate limiting per customer, and you need to transform XML requests to JSON for Lambda.

    $1

    | Monthly Requests | REST API Cost | HTTP API Cost | Savings |

    |-----------------|---------------|---------------|---------|

    | 1 million | $3.50 | $1.00 | 71% |

    | 10 million | $35.00 | $10.00 | 71% |

    | 100 million | $350.00 | $100.00 | 71% |

    | 1 billion | $3,500.00 | $1,000.00 | 71% |

    $1

    $1

    | Approach | Latency | Flexibility | Complexity | Cost |

    |----------|---------|-------------|------------|------|

    | Cognito User Pools | Low | Medium | Low | Free tier available |

    | Lambda Authorizer | Medium | High | Medium | Per-invocation cost |

    | IAM (SigV4) | Low | Low | Medium | No additional cost |

    | API Keys | Lowest | Lowest | Lowest | No additional cost |

    Senior insight: Lambda Authorizers add 50-200ms to cold requests. Cache the authorization decision aggressively (TTL up to 3600s) to amortize this cost. For high-throughput APIs, the authorization Lambda cost can exceed your API Gateway costs.

    $1

    | Cache TTL | Hit Rate | Freshness | Cost Impact |

    |-----------|----------|-----------|-------------|

    | 0 (disabled) | 0% | Perfect | Highest backend cost |

    | 60 seconds | ~60-80% | Good | Medium |

    | 300 seconds | ~85-95% | Acceptable | Lower |

    | 3600 seconds | ~95%+ | Stale risk | Lowest |

    Warning: API Gateway caching is per-stage, not per-customer. If you cache user-specific data without including user identity in the cache key, you'll serve User A's data to User B. Always include authentication headers in your cache key parameters for user-specific endpoints.

    $1

    Despite its capabilities, API Gateway isn't always the right choice:

    $1

  • You're running containers (ECS, EKS) with long-running HTTP connections
  • You need sticky sessions
  • You have predictable traffic patterns and want reserved capacity pricing
  • WebSocket usage exceeds API Gateway's 10,000 concurrent connection limit
  • $1

  • You need sub-10ms response times at the edge
  • Your API responses are highly cacheable
  • You're building a global API with edge computing requirements
  • $1

  • You need GraphQL with real-time subscriptions
  • You want automatic DynamoDB resolvers
  • Your mobile app needs offline-first capabilities with built-in sync
  • $1

    $1

    A well-designed OpenAPI specification serves as both documentation and contract:

    `yaml

    openapi: 3.0.0

    info:

    title: Product API

    version: 1.0.0

    paths:

    /products:

    get:

    summary: List products

    parameters:

    - name: category

    in: query

    schema:

    type: string

    - name: limit

    in: query

    schema:

    type: integer

    minimum: 1

    maximum: 100

    default: 20

    - name: cursor

    in: query

    description: Pagination cursor for next page

    schema:

    type: string

    responses:

    '200':

    description: List of products

    content:

    application/json:

    schema:

    type: object

    properties:

    items:

    type: array

    items:

    $ref: '#/components/schemas/Product'

    nextCursor:

    type: string

    `

    $1

    Structure your Lambda handlers to maximize reusability and testability:

    `javascript

    // handler.js - API Gateway Lambda integration

    const { getProducts, createProduct, updateProduct, deleteProduct } = require('./services/products');

    exports.handler = async (event) => {

    const pathParams = event.pathParameters || {};

    const queryParams = event.queryStringParameters || {};

    const body = event.body ? JSON.parse(event.body) : {};

    // Add request ID for distributed tracing

    const requestId = event.requestContext?.requestId;

    try {

    let result;

    switch (event.httpMethod) {

    case 'GET':

    result = pathParams.id

    ? await getProducts.byId(pathParams.id)

    : await getProducts.list(queryParams);

    break;

    case 'POST':

    result = await createProduct(body);

    return formatResponse(201, result, requestId);

    case 'PUT':

    result = await updateProduct(pathParams.id, body);

    break;

    case 'DELETE':

    await deleteProduct(pathParams.id);

    return formatResponse(204, null, requestId);

    default:

    return formatResponse(405, { error: 'Method not allowed' }, requestId);

    }

    return formatResponse(200, result, requestId);

    } catch (error) {

    console.error('Request failed:', { requestId, error: error.message, stack: error.stack });

    if (error.name === 'ValidationError') {

    return formatResponse(400, { error: error.message }, requestId);

    }

    if (error.name === 'NotFoundError') {

    return formatResponse(404, { error: 'Resource not found' }, requestId);

    }

    return formatResponse(500, { error: 'Internal server error' }, requestId);

    }

    };

    function formatResponse(statusCode, body, requestId) {

    return {

    statusCode,

    headers: {

    'Content-Type': 'application/json',

    'X-Request-Id': requestId,

    'Cache-Control': statusCode === 200 ? 'max-age=60' : 'no-store',

    },

    body: body ? JSON.stringify(body) : '',

    };

    }

    `

    $1

    Skip Lambda entirely for simple CRUD operations. This pattern reduces latency and cost:

    `yaml

    API Gateway -> DynamoDB direct integration

    Resources:

    GetItemMethod:

    Type: AWS::ApiGateway::Method

    Properties:

    RestApiId: !Ref Api

    ResourceId: !Ref ItemResource

    HttpMethod: GET

    AuthorizationType: AWS_IAM

    Integration:

    Type: AWS

    IntegrationHttpMethod: POST

    Uri: !Sub arn:aws:apigateway:${AWS::Region}:dynamodb:action/GetItem

    Credentials: !GetAtt ApiGatewayRole.Arn

    RequestTemplates:

    application/json: |

    {

    "TableName": "Items",

    "Key": {

    "id": {"S": "$input.params('id')"}

    }

    }

    IntegrationResponses:

    - StatusCode: 200

    ResponseTemplates:

    application/json: |

    #set($item = $input.path('$.Item'))

    {

    "id": "$item.id.S",

    "name": "$item.name.S",

    "price": $item.price.N

    }

    `

    $1

    $1

  • [ ] Use consistent naming conventions (kebab-case for URLs)
  • [ ] Implement proper versioning (path-based: /v1/products)
  • [ ] Design for backward compatibility
  • [ ] Use appropriate HTTP methods and status codes
  • [ ] Implement cursor-based pagination for large datasets
  • $1

  • [ ] Enable AWS WAF for REST APIs
  • [ ] Use Cognito or Lambda authorizers (never API keys alone for authentication)
  • [ ] Implement request validation at the API Gateway level
  • [ ] Enable CloudTrail logging for audit compliance
  • [ ] Use HTTPS only (API Gateway enforces this by default)
  • $1

  • [ ] Enable caching for read-heavy endpoints
  • [ ] Set appropriate timeout values (29s max for Lambda)
  • [ ] Implement throttling to protect backends
  • [ ] Use regional endpoints unless you need edge optimization
  • [ ] Enable gzip compression for large responses
  • $1

  • [ ] Set up CloudWatch alarms for 4xx/5xx error rates
  • [ ] Configure access logging with structured JSON
  • [ ] Enable X-Ray tracing for distributed debugging
  • [ ] Monitor cache hit ratios and adjust TTLs accordingly
  • [ ] Track latency percentiles (p50, p95, p99)
  • $1

    `

    Need WebSocket support?

    ├── Yes → REST API (WebSocket APIs)

    └── No → Do you need:

    ├── WAF integration? → REST API

    ├── API Keys/Usage Plans? → REST API

    ├── Request transformation? → REST API

    ├── Caching at Gateway? → REST API

    └── None of above? → HTTP API (save 71%)

    ``

    $1

    1. [API Gateway Documentation](https://docs.aws.amazon.com/apigateway/)

    2. [Choosing Between REST APIs and HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html)

    3. [Best Practices](https://docs.aws.amazon.com/apigateway/latest/developerguide/best-practices.html)

    4. [Security Best Practices](https://docs.aws.amazon.com/apigateway/latest/developerguide/security.html)

    5. [Performance Optimization Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-performance.html)

    Why This Matters

    Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.

    Trade-offs to Consider

    Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.

    When NOT to Use This

    Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.

    Decision Framework

    Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.