Full Serverless Application — End-to-End Architecture

Why Build a Full Serverless Application?

Individual serverless services are powerful, but their true value comes from combining them into a complete architecture. This chapter walks through building a production-ready serverless application that integrates Lambda, API Gateway, DynamoDB, Step Functions, EventBridge, S3, and Cognito.

Why this matters for your career:

  • Real-world serverless applications combine multiple AWS services
  • Understanding the full architecture is essential for AWS certification
  • Serverless architecture design is a common interview topic for cloud roles
  • Building end-to-end serverless apps is a high-value freelance skill

Architecture Overview

                    ┌──────────┐
                    │  Cognito  │  (User authentication)
                    └─────┬────┘
                          │ JWT Token
                    ┌─────▼────┐
Client (Browser) ──►│API Gateway│  (HTTP routing, auth, throttling)
                    └─────┬────┘
                          │
                    ┌─────▼────┐
                    │  Lambda  │  (Business logic)
                    └─────┬────┘
                          │
              ┌───────────┼───────────┐
              │           │           │
        ┌─────▼────┐ ┌───▼───┐ ┌───▼────┐
        │ DynamoDB │ │ Step  │ │EventBr.│  (Database, orchestration, events)
        └──────────┘ │Fnctns.│ └────────┘
                     └───────┘
                          │
                    ┌─────▼────┐
                    │    S3    │  (File storage)
                    └──────────┘

Authentication (Cognito)

Amazon Cognito provides user sign-up, sign-in, and access control.

Resources:
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: MyAppUsers
      AutoVerifiedAttributes: [email]
      Policies:
        PasswordPolicy:
          MinimumLength: 8
          RequireUppercase: true
          RequireLowercase: true
          RequireNumbers: true

  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      ClientName: MyAppClient
      UserPoolId: !Ref UserPool
      GenerateSecret: false
      ExplicitAuthFlows:
        - ALLOW_USER_PASSWORD_AUTH
        - ALLOW_REFRESH_TOKEN_AUTH

API Layer (API Gateway + Lambda)

# SAM template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Runtime: python3.12
    Timeout: 30
    Environment:
      Variables:
        TABLE_NAME: !Ref UsersTable

Resources:
  # API Gateway (HTTP API)
  MyApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      Auth:
        Authorizers:
          CognitoAuthorizer:
            IdentitySource: $request.header.Authorization
            Jwt:
              Issuer: !Sub https://cognito-idp.${AWS::Region}.amazonaws.com/${UserPool}
              Audience:
                - !Ref UserPoolClient

  # Lambda Functions
  CreateUserFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/create_user/
      Handler: app.lambda_handler
      Events:
        CreateUser:
          Type: HttpApi
          Properties:
            ApiId: !Ref MyApi
            Path: /users
            Method: POST
            Auth:
              Authorizer: CognitoAuthorizer
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref UsersTable

  GetUserFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/get_user/
      Handler: app.lambda_handler
      Events:
        GetUser:
          Type: HttpApi
          Properties:
            ApiId: !Ref MyApi
            Path: /users/{id}
            Method: GET
            Auth:
              Authorizer: CognitoAuthorizer
      Policies:
        - DynamoDBReadPolicy:
            TableName: !Ref UsersTable

Database (DynamoDB)

  UsersTable:
    Type: AWS::DynamoDB::Table
    Properties:
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      TimeToLiveSpecification:
        AttributeName: ttl
        Enabled: true

  OrdersTable:
    Type: AWS::DynamoDB::Table
    Properties:
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: userId
          AttributeType: S
        - AttributeName: createdAt
          AttributeType: S
      KeySchema:
        - AttributeName: userId
          KeyType: HASH
        - AttributeName: createdAt
          KeyType: RANGE
      StreamSpecification:
        StreamViewType: NEW_AND_OLD_IMAGES

Workflow Orchestration (Step Functions)

  OrderWorkflow:
    Type: AWS::Serverless::StateMachine
    Properties:
      DefinitionUri: workflows/order-workflow.asl.json
      Events:
        OrderCreated:
          Type: EventBridge
          Properties:
            EventBusName: default
            Pattern:
              source:
                - myapp.orders
              detail-type:
                - OrderCreated
      Policies:
        - LambdaInvokePolicy:
            FunctionName: !Ref ProcessPaymentFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref UpdateInventoryFunction
        - LambdaInvokePolicy:
            FunctionName: !Ref SendNotificationFunction

Event-Driven Processing (EventBridge + DynamoDB Streams)

  OrderStreamProcessor:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/order_stream_processor/
      Handler: app.lambda_handler
      Events:
        Stream:
          Type: DynamoDB
          Properties:
            Stream: !GetAtt OrdersTable.StreamArn
            BatchSize: 100
            StartingPosition: LATEST
      Policies:
        - DynamoDBReadPolicy:
            TableName: !Ref OrdersTable
        - SQSSendMessagePolicy:
            QueueName: !Ref OrderDLQ

  OrderDLQ:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: order-dlq
      MessageRetentionPeriod: 1209600  # 14 days

File Storage (S3)

  UploadsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: myapp-uploads-${AWS::AccountId}
      CorsConfiguration:
        CorsRules:
          - AllowedOrigins: ['*']
            AllowedMethods: [PUT, POST]
            AllowedHeaders: ['*']
            MaxAgeSeconds: 3600
      LifecycleConfiguration:
        Rules:
          - Id: ExpireOldFiles
            Status: Enabled
            ExpirationInDays: 365

Deployment with SAM

# Build
sam build

# Deploy to AWS
sam deploy --guided
# Follow prompts: stack name, region, confirm changes, etc.

# Outputs:
# - API endpoint URL
# - Cognito User Pool ID
# - S3 bucket name

Complete Lambda Function Example

# functions/create_user/app.py
import json
import os
import uuid
from datetime import datetime

import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])
eventbridge = boto3.client('events')

def lambda_handler(event, context):
    body = json.loads(event['body'])
    user_id = str(uuid.uuid4())

    item = {
        'id': user_id,
        'name': body['name'],
        'email': body['email'],
        'plan': body.get('plan', 'free'),
        'createdAt': datetime.utcnow().isoformat() + 'Z',
        'updatedAt': datetime.utcnow().isoformat() + 'Z'
    }

    table.put_item(Item=item)

    # Emit event for downstream processing
    eventbridge.put_events(Entries=[{
        'Source': 'myapp.users',
        'DetailType': 'UserCreated',
        'Detail': json.dumps({'userId': user_id, 'plan': item['plan']}),
        'EventBusName': 'default'
    }])

    return {
        'statusCode': 201,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        'body': json.dumps(item)
    }

Testing the Full Application

# 1. Deploy
sam deploy

# 2. Get the API URL from SAM outputs
# OutputKey: MyApiEndpoint

# 3. Create a user via the API
curl -X POST https://api-id.execute-api.us-east-1.amazonaws.com/users \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <cognito-jwt-token>" \
  -d '{"name": "Alice", "email": "alice@example.com"}'

# 4. Get the user
curl https://api-id.execute-api.us-east-1.amazonaws.com/users/abc123 \
  -H "Authorization: Bearer <cognito-jwt-token>"

# 5. Check CloudWatch Logs
aws logs describe-log-groups --log-group-name-prefix /aws/lambda/create-user
aws logs tail /aws/lambda/create-user --since 5m

# 6. Check DynamoDB
aws dynamodb scan --table-name UsersTable

# 7. Check Step Functions execution
aws stepfunctions list-executions --state-machine-arn <arn>

Best Practices

| Practice | Reason | |----------|--------| | Use AWS SAM or CDK for infrastructure | Repeatable, version-controlled deployments | | Apply least-privilege IAM policies | Security — only grant required permissions | | Use environment variables for configuration | No hardcoded values in Lambda code | | Enable X-Ray tracing | Debug distributed serverless applications | | Set up CloudWatch dashboards | Monitor API errors, Lambda throttles, DynamoDB throttling | | Implement structured logging | Easier log analysis and alerting | | Use dead letter queues for failures | Never lose failed events | | Enable DynamoDB auto-scaling | Handle traffic spikes without manual intervention |

Summary

A complete serverless application combines Cognito (auth), API Gateway (routing), Lambda (logic), DynamoDB (data), Step Functions (workflows), EventBridge (events), and S3 (storage). Use AWS SAM or CDK to define and deploy the entire stack as code.

Key takeaways:

  • Cognito handles user authentication with JWT tokens
  • API Gateway routes HTTP requests and enforces auth
  • Lambda executes business logic with environment variables
  • DynamoDB stores data with on-demand capacity for serverless
  • Step Functions orchestrates multi-step workflows
  • EventBridge enables event-driven communication
  • S3 stores files and static assets
  • Define everything with infrastructure as code (SAM/CDK)
  • Enable X-Ray, CloudWatch, and DLQs for production readiness
  • Use least-privilege IAM policies for security

What's Next: DevOps — GitOps

The next course covers GitOps — using Git as the single source of truth for infrastructure and application deployment with ArgoCD.

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!