Skip to main content
AWS SAM Architecture

Module Overview

Estimated Time: 3-4 hours | Difficulty: Intermediate | Prerequisites: Lambda, CloudFormation basics
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. 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?

Simplified Syntax

Write less CloudFormation—SAM handles the boilerplate

Local Testing

Test Lambda functions and APIs locally before deployment

Best Practices Built-in

Automatic IAM policies, API Gateway configuration, and more

Portable

Deploy anywhere—SAM transforms to standard CloudFormation

SAM Template Structure

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: My Serverless Application

# Global configuration for all functions
Globals:
  Function:
    Runtime: python3.11
    Timeout: 30
    MemorySize: 256
    Tracing: Active
    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
      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

MyFunction:
  Type: AWS::Serverless::Function
  Properties:
    # Required
    Handler: index.handler
    Runtime: python3.11
    CodeUri: ./src
    
    # Optional - Compute
    MemorySize: 512
    Timeout: 30
    Architectures:
      - arm64  # Graviton2 for cost savings
    
    # Optional - VPC
    VpcConfig:
      SecurityGroupIds:
        - !Ref LambdaSecurityGroup
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
    
    # Optional - Tracing and Logging
    Tracing: Active
    LoggingConfig:
      LogFormat: JSON
      ApplicationLogLevel: INFO
    
    # Optional - Concurrency
    ReservedConcurrentExecutions: 100
    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

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

# HTTP API (API Gateway v2) - simpler, cheaper
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

# 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

# Initialize new project
sam init --runtime python3.11 --name my-app

# Build the application
sam build

# Start local API Gateway
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

# 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

# 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

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:
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

# 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

# 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

# .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

Use Globals

Define common settings in Globals section to reduce repetition

Layer Dependencies

Put shared dependencies in Lambda Layers for faster deployments

Use Policy Templates

SAM policy templates follow least privilege by default

Local Testing

Always test locally before deploying—it’s faster and free

🎯 Interview Questions

SAM:
  • Best for pure serverless applications
  • Simplified syntax, built-in best practices
  • Excellent local testing
CDK:
  • Programmatic (TypeScript, Python)
  • Better for complex infrastructure
  • More flexibility
Terraform:
  • Multi-cloud support
  • Established ecosystem
  • State management
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.

Next Module

AWS GuardDuty

Intelligent threat detection for your AWS environment