> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AWS SAM

> Build, test, and deploy serverless applications with the Serverless Application Model

<Frame>
  <img src="https://mintcdn.com/devweeekends/sTu6A4whRFPJo0_g/images/aws/sam-architecture.svg?fit=max&auto=format&n=sTu6A4whRFPJo0_g&q=85&s=a7082f6cd5b8c0eb1725b5b74529aab9" alt="AWS SAM Architecture" width="1080" height="1080" data-path="images/aws/sam-architecture.svg" />
</Frame>

## Module Overview

<Info>
  **Estimated Time**: 3-4 hours | **Difficulty**: Intermediate | **Prerequisites**: Lambda, CloudFormation basics
</Info>

AWS SAM (Serverless Application Model) is an open-source framework for building serverless applications. It extends CloudFormation with simplified syntax and provides a CLI for local development and deployment. Think of SAM as CloudFormation's serverless-specific dialect -- you write 20 lines of SAM and it expands into 200 lines of CloudFormation behind the scenes. The trade-off is less flexibility (you cannot control every CloudFormation property), but for 90% of serverless use cases, SAM's defaults are exactly what a senior engineer would configure anyway.

**What You'll Learn:**

* SAM template structure and syntax
* Local development and testing
* Deployment and CI/CD integration
* Common serverless patterns
* SAM CLI commands and workflows

***

## Why SAM?

<CardGroup cols={2}>
  <Card title="Simplified Syntax" icon="code">
    Write less CloudFormation—SAM handles the boilerplate
  </Card>

  <Card title="Local Testing" icon="laptop-code">
    Test Lambda functions and APIs locally before deployment
  </Card>

  <Card title="Best Practices Built-in" icon="check-circle">
    Automatic IAM policies, API Gateway configuration, and more
  </Card>

  <Card title="Portable" icon="box">
    Deploy anywhere—SAM transforms to standard CloudFormation
  </Card>
</CardGroup>

***

## SAM Template Structure

```yaml theme={null}
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: My Serverless Application

# Globals apply to ALL functions in the template -- the DRY principle for
# serverless. Instead of repeating Runtime and Timeout on every function,
# set them once here. Individual functions can override any Global value.
Globals:
  Function:
    Runtime: python3.11
    Timeout: 30
    MemorySize: 256
    Tracing: Active  # Enables X-Ray on all functions
    Environment:
      Variables:
        LOG_LEVEL: INFO
        TABLE_NAME: !Ref OrdersTable

Parameters:
  Environment:
    Type: String
    Default: dev
    AllowedValues: [dev, staging, prod]

Resources:
  # Lambda Function
  OrderFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: app.lambda_handler
      CodeUri: src/
      Description: Process orders
      Events:
        ApiEvent:
          Type: Api
          Properties:
            Path: /orders
            Method: POST
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref OrdersTable
      
  # DynamoDB Table
  OrdersTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Sub ${Environment}-orders
      # PAY_PER_REQUEST (on-demand) is ideal for dev/staging and unpredictable
      # workloads. For steady production traffic (>100 reads/sec consistently),
      # PROVISIONED with auto-scaling is 20-30% cheaper.
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: order_id
          AttributeType: S
      KeySchema:
        - AttributeName: order_id
          KeyType: HASH

Outputs:
  ApiUrl:
    Description: API Gateway URL
    Value: !Sub https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/
```

***

## SAM Resource Types

### AWS::Serverless::Function

```yaml theme={null}
MyFunction:
  Type: AWS::Serverless::Function
  Properties:
    # Required
    Handler: index.handler
    Runtime: python3.11
    CodeUri: ./src
    
    # Optional - Compute
    MemorySize: 512
    Timeout: 30
    Architectures:
      - arm64  # Graviton2: ~20% cheaper and ~34% better price-performance
               # than x86. Use arm64 unless you have native x86 dependencies.
    
    # Optional - VPC
    VpcConfig:
      SecurityGroupIds:
        - !Ref LambdaSecurityGroup
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
    
    # Optional - Tracing and Logging
    Tracing: Active
    LoggingConfig:
      LogFormat: JSON
      ApplicationLogLevel: INFO
    
    # Optional - Concurrency
    # ReservedConcurrency caps the max concurrent executions of THIS function.
    # Use it as a safety valve: if a downstream DB can only handle 100
    # connections, cap Lambda at 100 to prevent overwhelming it.
    ReservedConcurrentExecutions: 100
    # ProvisionedConcurrency keeps N instances warm at all times -- eliminates
    # cold starts but costs ~$0.015/GB-hr whether invoked or not.
    # Cost tip: Only use this for latency-sensitive endpoints (e.g., user-facing
    # APIs). For async/batch work, cold starts are irrelevant.
    ProvisionedConcurrencyConfig:
      ProvisionedConcurrentExecutions: 10
    
    # Event Sources
    Events:
      ApiEvent:
        Type: Api
        Properties:
          Path: /items
          Method: GET
      ScheduleEvent:
        Type: Schedule
        Properties:
          Schedule: rate(1 hour)
      SQSEvent:
        Type: SQS
        Properties:
          Queue: !GetAtt MyQueue.Arn
          BatchSize: 10
      S3Event:
        Type: S3
        Properties:
          Bucket: !Ref MyBucket
          Events: s3:ObjectCreated:*
    
    # Permissions
    Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref MyTable
      - S3ReadPolicy:
          BucketName: !Ref MyBucket
      - Statement:
          - Effect: Allow
            Action:
              - secretsmanager:GetSecretValue
            Resource: !Ref MySecret
```

### AWS::Serverless::Api

```yaml theme={null}
MyApi:
  Type: AWS::Serverless::Api
  Properties:
    StageName: prod
    
    # Enable tracing
    TracingEnabled: true
    
    # CORS configuration
    Cors:
      AllowMethods: "'GET,POST,OPTIONS'"
      AllowHeaders: "'Content-Type,Authorization'"
      AllowOrigin: "'https://myapp.com'"
    
    # Custom domain
    Domain:
      DomainName: api.myapp.com
      CertificateArn: !Ref Certificate
      Route53:
        HostedZoneId: Z1234567890
    
    # Authentication
    Auth:
      DefaultAuthorizer: MyCognitoAuthorizer
      Authorizers:
        MyCognitoAuthorizer:
          UserPoolArn: !GetAtt UserPool.Arn
    
    # Models and validation
    Models:
      OrderModel:
        type: object
        required:
          - product_id
          - quantity
        properties:
          product_id:
            type: string
          quantity:
            type: integer
            minimum: 1
```

### AWS::Serverless::HttpApi

```yaml theme={null}
# HTTP API (API Gateway v2) - simpler, cheaper
# Cost comparison: HTTP API costs $1.00/million requests vs REST API at
# $3.50/million. HTTP API also has lower latency (~10ms vs ~29ms added).
# Use REST API only when you need: request validation, WAF integration,
# API keys/usage plans, or caching. For most microservice-to-microservice
# or simple CRUD APIs, HTTP API is the better choice.
MyHttpApi:
  Type: AWS::Serverless::HttpApi
  Properties:
    StageName: prod
    CorsConfiguration:
      AllowOrigins:
        - "https://myapp.com"
      AllowMethods:
        - GET
        - POST
      AllowHeaders:
        - Content-Type

MyFunction:
  Type: AWS::Serverless::Function
  Properties:
    Events:
      HttpApiEvent:
        Type: HttpApi
        Properties:
          ApiId: !Ref MyHttpApi
          Path: /items
          Method: GET
```

### Other SAM Resources

```yaml theme={null}
# Step Functions
MyStateMachine:
  Type: AWS::Serverless::StateMachine
  Properties:
    DefinitionUri: statemachine/definition.asl.json
    Policies:
      - LambdaInvokePolicy:
          FunctionName: !Ref ProcessFunction

# Application (nested stack)
MyNestedApp:
  Type: AWS::Serverless::Application
  Properties:
    Location:
      ApplicationId: arn:aws:serverlessrepo:us-east-1:123456789012:applications/my-app
      SemanticVersion: 1.0.0

# Layer
MyLayer:
  Type: AWS::Serverless::LayerVersion
  Properties:
    LayerName: my-dependencies
    ContentUri: layers/
    CompatibleRuntimes:
      - python3.11
    RetentionPolicy: Retain
```

***

## SAM CLI Commands

```
┌────────────────────────────────────────────────────────────────────────┐
│                    SAM CLI Workflow                                     │
├────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   Development Workflow:                                                 │
│   ─────────────────────                                                │
│                                                                         │
│   sam init                    # Create new project from template       │
│        │                                                                │
│        ▼                                                                │
│   sam build                   # Build and package application          │
│        │                                                                │
│        ▼                                                                │
│   sam local invoke            # Test single function locally           │
│   sam local start-api         # Start local API Gateway                │
│        │                                                                │
│        ▼                                                                │
│   sam validate                # Validate template                       │
│        │                                                                │
│        ▼                                                                │
│   sam deploy --guided         # Deploy to AWS (first time)             │
│   sam deploy                  # Deploy using saved config              │
│        │                                                                │
│        ▼                                                                │
│   sam logs -n MyFunction      # View CloudWatch logs                   │
│   sam traces                  # View X-Ray traces                      │
│                                                                         │
└────────────────────────────────────────────────────────────────────────┘
```

### Local Development

```bash theme={null}
# Initialize new project
sam init --runtime python3.11 --name my-app

# Build the application
sam build

# Start local API Gateway -- spins up a Docker container mimicking
# API Gateway + Lambda. Requests to localhost:3000 invoke your handler.
# Common mistake: Assuming local behavior matches production. Local invoke
# does NOT simulate IAM permissions, VPC networking, or concurrency limits.
# Always run integration tests against a deployed stack before production.
sam local start-api --port 3000

# Invoke function with event file
sam local invoke MyFunction --event events/order.json

# Generate sample event
sam local generate-event apigateway aws-proxy > events/api.json
sam local generate-event s3 put > events/s3.json

# Start Lambda with hot reload
sam local start-lambda --host 0.0.0.0

# Debug with VS Code (attach debugger on port 5858)
sam local invoke -d 5858 MyFunction --event event.json
```

### Deployment

```bash theme={null}
# First deployment (interactive)
sam deploy --guided
# Prompts for:
# - Stack name
# - Region
# - Confirm IAM role creation
# - Allow SAM to create resources

# Subsequent deployments
sam deploy

# Deploy to specific environment
sam deploy --config-env prod

# Deploy with parameter overrides
sam deploy --parameter-overrides Environment=prod LogLevel=WARN

# View deployed stack
sam list stacks

# Delete stack
sam delete --stack-name my-stack
```

### Debugging and Monitoring

```bash theme={null}
# Tail logs in real-time
sam logs -n MyFunction --tail

# Get logs for specific time range
sam logs -n MyFunction --start-time "5 minutes ago"

# View X-Ray traces
sam traces

# Sync changes in real-time (development)
sam sync --watch --stack-name my-stack
```

***

## Project Structure

```
my-serverless-app/
├── template.yaml           # SAM template
├── samconfig.toml          # Deployment configuration
├── src/
│   ├── orders/
│   │   ├── __init__.py
│   │   ├── app.py          # Lambda handler
│   │   └── requirements.txt
│   ├── payments/
│   │   ├── app.py
│   │   └── requirements.txt
│   └── shared/
│       └── utils.py
├── layers/
│   └── python/
│       └── requirements.txt
├── events/
│   ├── order_created.json
│   └── api_request.json
├── tests/
│   ├── unit/
│   │   └── test_handler.py
│   └── integration/
│       └── test_api.py
└── statemachines/
    └── order_workflow.asl.json
```

### samconfig.toml

```toml theme={null}
version = 0.1

[default.deploy.parameters]
stack_name = "my-app-dev"
region = "us-east-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
parameter_overrides = "Environment=dev"

[prod.deploy.parameters]
stack_name = "my-app-prod"
region = "us-east-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"
parameter_overrides = "Environment=prod"
```

***

## Policy Templates

SAM provides policy templates for common use cases:

```yaml theme={null}
Policies:
  # DynamoDB
  - DynamoDBCrudPolicy:
      TableName: !Ref MyTable
  - DynamoDBReadPolicy:
      TableName: !Ref MyTable
  - DynamoDBStreamReadPolicy:
      TableName: !Ref MyTable
      StreamName: !GetAtt MyTable.StreamArn
  
  # S3
  - S3ReadPolicy:
      BucketName: !Ref MyBucket
  - S3WritePolicy:
      BucketName: !Ref MyBucket
  - S3CrudPolicy:
      BucketName: !Ref MyBucket
  
  # SQS
  - SQSPollerPolicy:
      QueueName: !GetAtt MyQueue.QueueName
  - SQSSendMessagePolicy:
      QueueName: !GetAtt MyQueue.QueueName
  
  # SNS
  - SNSPublishMessagePolicy:
      TopicName: !GetAtt MyTopic.TopicName
  
  # Lambda
  - LambdaInvokePolicy:
      FunctionName: !Ref OtherFunction
  
  # Secrets Manager
  - AWSSecretsManagerGetSecretValuePolicy:
      SecretArn: !Ref MySecret
  
  # Step Functions
  - StepFunctionsExecutionPolicy:
      StateMachineName: !GetAtt MyStateMachine.Name
  
  # EventBridge
  - EventBridgePutEventsPolicy:
      EventBusName: default
```

***

## Testing

### Unit Tests

```python theme={null}
# tests/unit/test_handler.py
import pytest
from unittest.mock import patch, MagicMock
from src.orders.app import lambda_handler

@pytest.fixture
def api_gateway_event():
    return {
        "httpMethod": "POST",
        "path": "/orders",
        "body": '{"product_id": "P001", "quantity": 2}',
        "requestContext": {
            "requestId": "test-request-id"
        }
    }

@pytest.fixture
def lambda_context():
    context = MagicMock()
    context.function_name = "test-function"
    context.memory_limit_in_mb = 256
    context.invoked_function_arn = "arn:aws:lambda:us-east-1:123456789012:function:test"
    context.aws_request_id = "test-request-id"
    return context

@patch('src.orders.app.dynamodb_table')
def test_create_order_success(mock_table, api_gateway_event, lambda_context):
    mock_table.put_item.return_value = {}
    
    response = lambda_handler(api_gateway_event, lambda_context)
    
    assert response['statusCode'] == 201
    mock_table.put_item.assert_called_once()

@patch('src.orders.app.dynamodb_table')
def test_create_order_validation_error(mock_table, api_gateway_event, lambda_context):
    api_gateway_event['body'] = '{"product_id": ""}'  # Invalid
    
    response = lambda_handler(api_gateway_event, lambda_context)
    
    assert response['statusCode'] == 400
```

### Integration Tests

```python theme={null}
# tests/integration/test_api.py
import requests
import pytest
import os

API_URL = os.environ.get('API_URL', 'https://xxx.execute-api.us-east-1.amazonaws.com/Prod')

@pytest.mark.integration
def test_create_order_integration():
    response = requests.post(
        f"{API_URL}/orders",
        json={"product_id": "P001", "quantity": 2},
        headers={"Content-Type": "application/json"}
    )
    
    assert response.status_code == 201
    data = response.json()
    assert "order_id" in data

@pytest.mark.integration
def test_get_order_integration():
    # First create an order
    create_response = requests.post(
        f"{API_URL}/orders",
        json={"product_id": "P001", "quantity": 1}
    )
    order_id = create_response.json()["order_id"]
    
    # Then retrieve it
    get_response = requests.get(f"{API_URL}/orders/{order_id}")
    
    assert get_response.status_code == 200
    assert get_response.json()["order_id"] == order_id
```

***

## CI/CD Integration

### GitHub Actions

```yaml theme={null}
# .github/workflows/deploy.yml
name: Deploy SAM Application

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - uses: aws-actions/setup-sam@v2
      
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1
      
      - name: Build
        run: sam build
      
      - name: Run Tests
        run: |
          pip install pytest
          pytest tests/unit/
      
      - name: Deploy
        run: sam deploy --no-confirm-changeset --no-fail-on-empty-changeset
```

***

## Best Practices

<CardGroup cols={2}>
  <Card title="Use Globals" icon="globe">
    Define common settings in Globals section to reduce repetition
  </Card>

  <Card title="Layer Dependencies" icon="layer-group">
    Put shared dependencies in Lambda Layers for faster deployments
  </Card>

  <Card title="Use Policy Templates" icon="shield">
    SAM policy templates follow least privilege by default
  </Card>

  <Card title="Local Testing" icon="laptop">
    Always test locally before deploying—it's faster and free
  </Card>
</CardGroup>

***

## 🎯 Interview Questions

<AccordionGroup>
  <Accordion title="Q1: SAM vs CDK vs Terraform?">
    **SAM:**

    * Best for pure serverless applications (Lambda + API Gateway + DynamoDB)
    * Simplified syntax, built-in best practices, least boilerplate
    * Excellent local testing with `sam local`
    * Limitation: Awkward when you mix serverless with traditional resources (VPCs, ECS)

    **CDK:**

    * Programmatic (TypeScript, Python) -- real programming constructs, loops, conditionals
    * Better for complex infrastructure that mixes serverless and non-serverless
    * Steeper learning curve but more flexible
    * A senior engineer would say: "Use CDK when your SAM template starts needing CloudFormation escape hatches everywhere"

    **Terraform:**

    * Multi-cloud support -- the only real option if you deploy to AWS AND GCP/Azure
    * Established ecosystem with mature state management
    * More verbose for serverless, but consistent across all infrastructure
    * Common mistake: Choosing Terraform for a pure-serverless AWS project just because your team knows it -- SAM or CDK will be 3x less code
  </Accordion>

  <Accordion title="Q2: How does SAM local invoke work?">
    SAM CLI:

    1. Builds Docker container matching Lambda runtime
    2. Mounts your code into container
    3. Invokes handler with event
    4. Returns response

    Supports debugging with port attachment.
  </Accordion>
</AccordionGroup>

***

## Next Module

<Card title="AWS GuardDuty" icon="shield" href="/aws/guardduty">
  Intelligent threat detection for your AWS environment
</Card>
