On This Page
1Overview2Architecture Overview
3AWS Service Topology4Implementation Guide
5Decision Criteria6Cost Model
7Anti-Patterns to Avoid8References

Overview

The serverless pattern on AWS inverts the traditional deployment model. Instead of running a server continuously and routing requests to it, you deploy a function and AWS runs it only when triggered. The function scales automatically, runs in parallel for concurrent requests, and costs nothing when idle. This model is ideal for APIs with variable traffic, event processors, scheduled jobs, and workflow orchestration.

The key architectural decision in serverless is choosing between Lambda for single-operation functions and Step Functions for multi-step workflows requiring state, branching, and error handling across steps.

Architecture Overview

%%{init:{'theme':'base','themeVariables':{'fontSize':'14px','fontFamily':'IBM Plex Sans, system-ui, sans-serif','primaryColor':'#DBEAFE','primaryTextColor':'#1e3a5f','primaryBorderColor':'#2563EB','lineColor':'#374151','clusterBkg':'#F9FAFB','clusterBorder':'#D1D5DB','edgeLabelBackground':'#FFFFFF'},'flowchart':{'curve':'orthogonal','padding':30,'nodeSpacing':65,'rankSpacing':75,'useMaxWidth':true}}}%% flowchart TD START([Client Request or Event Trigger]) START --> TRIGGER{Trigger Type} TRIGGER -->|HTTP request| APIGW[API Gateway\nHTTP API or REST API\nAuthoriser, throttling, CORS] TRIGGER -->|Schedule| SCHED[EventBridge Scheduler\nCron or rate expression\nRetry with DLQ] TRIGGER -->|Queue message| SQS_T[SQS Queue\nBatch processing\nDead letter queue] TRIGGER -->|Multi-step workflow| SFN[Step Functions\nState machine\nError handling per step] APIGW --> LAMBDA[AWS Lambda Function\nStateless execution\nVPC or public network] SCHED --> LAMBDA SQS_T --> LAMBDA SFN --> LAMBDA LAMBDA --> DECISION{Response Type} DECISION -->|Synchronous| RESP[Return response\nto API Gateway] DECISION -->|Async continuation| NEXT[Publish event\nto EventBridge or SQS] DECISION -->|Workflow step| SFN RESP --> END([Response Returned to Client]) NEXT --> END style START fill:#4f8ef7,color:#fff style END fill:#10b981,color:#fff style SFN fill:#e0f2fe style LAMBDA fill:#e0f2fe

AWS Service Topology

%%{init:{'theme':'base','themeVariables':{'fontSize':'14px','fontFamily':'IBM Plex Sans, system-ui, sans-serif','primaryColor':'#DBEAFE','primaryTextColor':'#1e3a5f','primaryBorderColor':'#2563EB','lineColor':'#374151','clusterBkg':'#F9FAFB','clusterBorder':'#D1D5DB','edgeLabelBackground':'#FFFFFF'},'flowchart':{'curve':'orthogonal','padding':30,'nodeSpacing':65,'rankSpacing':75,'useMaxWidth':true}}}%% flowchart TD CLIENT[Client Application\nWeb, Mobile, or Service] subgraph GATEWAY["API Gateway — HTTP API preferred or REST API"] AUTH[Lambda Authoriser\nJWT validation\nCustom business logic] THROTTLE[Usage Plans\nPer-client rate limiting\n10,000 RPS default limit] STAGE[Deployment Stages\ndev, staging, production\nCanary deployments] end subgraph FUNCTIONS["AWS Lambda"] FN_API[API Handler Functions\nNode.js 20 or Python 3.12\nArm64 Graviton2 runtime] FN_WARM[Provisioned Concurrency\nPre-warmed instances\nFor latency-sensitive paths] LAYER[Lambda Layers\nShared dependencies\nSDK and utility code] end subgraph WORKFLOW["AWS Step Functions — Express or Standard"] SFN_DEF[State Machine Definition\nAmazon States Language\nVisual workflow editor] SFN_ERR[Error Handling\nRetry with backoff\nCatch and compensate] end subgraph STORAGE["Data Layer"] DDB_SL[DynamoDB\nSingle-table design\nOn-demand billing] CACHE[ElastiCache Serverless\nRedis compatible\nPay per ECU used] S3_SL[Amazon S3\nLarge object storage\nPresigned URL pattern] end CLIENT --> AUTH --> THROTTLE --> STAGE STAGE --> FN_API FN_API --> LAYER FN_WARM -.->|Pre-warmed| FN_API FN_API --> DDB_SL & CACHE & S3_SL FN_API -->|Long workflow| SFN_DEF SFN_DEF --> SFN_ERR SFN_DEF --> FN_API style CLIENT fill:#4f8ef7,color:#fff style FN_WARM fill:#e0f2fe style SFN_ERR fill:#fef3c7

Implementation Guide

CDK Stack — Serverless REST API

import * as cdk from 'aws-cdk-lib';
import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2';
import * as integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import * as authorizers from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as logs from 'aws-cdk-lib/aws-logs';
import { Duration, RemovalPolicy } from 'aws-cdk-lib';

export class ServerlessApiStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Single-table DynamoDB design
    const table = new dynamodb.Table(this, 'AppTable', {
      partitionKey: { name: 'PK', type: dynamodb.AttributeType.STRING },
      sortKey: { name: 'SK', type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      pointInTimeRecovery: true,
      removalPolicy: RemovalPolicy.RETAIN,
    });

    // Lambda authoriser — JWT validation
    const authFunction = new lambda.Function(this, 'JwtAuthoriser', {
      runtime: lambda.Runtime.NODEJS_20_X,
      architecture: lambda.Architecture.ARM_64,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/authoriser'),
      environment: { JWKS_URI: process.env.JWKS_URI ?? '' },
      timeout: Duration.seconds(5),
    });

    const authorizer = new authorizers.HttpLambdaAuthorizer(
      'JwtAuthorizer', authFunction, {
        responseTypes: [authorizers.HttpLambdaResponseType.SIMPLE],
        resultsCacheTtl: Duration.minutes(5),
      });

    // API handler — Arm64 Graviton2 for 20% cost reduction
    const apiHandler = new lambda.Function(this, 'ApiHandler', {
      runtime: lambda.Runtime.NODEJS_20_X,
      architecture: lambda.Architecture.ARM_64,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda/api-handler'),
      environment: { TABLE_NAME: table.tableName },
      timeout: Duration.seconds(29),
      logRetention: logs.RetentionDays.ONE_MONTH,
    });

    table.grantReadWriteData(apiHandler);

    // HTTP API — lower cost than REST API
    const api = new apigwv2.HttpApi(this, 'AppApi', {
      corsPreflight: {
        allowOrigins: ['https://ascendion.engineering'],
        allowMethods: [apigwv2.CorsHttpMethod.GET,
                       apigwv2.CorsHttpMethod.POST,
                       apigwv2.CorsHttpMethod.PUT,
                       apigwv2.CorsHttpMethod.DELETE],
        allowHeaders: ['Authorization', 'Content-Type'],
      },
      defaultAuthorizer: authorizer,
    });

    api.addRoutes({
      path: '/api/{proxy+}',
      methods: [apigwv2.HttpMethod.ANY],
      integration: new integrations.HttpLambdaIntegration(
        'ApiIntegration', apiHandler),
    });
  }
}

Step Functions — Order Fulfilment Workflow

import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks';

const validateOrder = new tasks.LambdaInvoke(this, 'ValidateOrder', {
  lambdaFunction: validateFn,
  outputPath: '$.Payload',
});

const processPayment = new tasks.LambdaInvoke(this, 'ProcessPayment', {
  lambdaFunction: paymentFn,
  outputPath: '$.Payload',
  retryOnServiceExceptions: true,
});

const notifyCustomer = new tasks.LambdaInvoke(this, 'NotifyCustomer', {
  lambdaFunction: notifyFn,
});

const compensate = new tasks.LambdaInvoke(this, 'CompensateOrder', {
  lambdaFunction: compensateFn,
  comment: 'Reverse any completed steps on failure',
});

const orderFailed = new sfn.Fail(this, 'OrderFailed', {
  error: 'OrderProcessingFailed',
});

const definition = validateOrder
  .next(processPayment
    .addCatch(compensate.next(orderFailed), {
      errors: ['PaymentDeclined', 'PaymentTimeout'],
    }))
  .next(notifyCustomer);

new sfn.StateMachine(this, 'OrderWorkflow', {
  definition,
  stateMachineType: sfn.StateMachineType.EXPRESS,
  timeout: Duration.minutes(5),
  tracingEnabled: true,
});

Decision Criteria

Use Case Recommended Alternative Reason to Prefer Lambda
REST API endpoint Lambda + API Gateway HTTP API ECS Fargate No cold start concern for APIs with consistent traffic
Scheduled batch job Lambda + EventBridge Scheduler ECS scheduled task Lambda free tier covers most batch jobs
Multi-step workflow Step Functions + Lambda Lambda orchestrating Lambda Step Functions externalises state, retries, error handling
Long-running job over 15min ECS Fargate Lambda Lambda hard limit is 15 minutes
Persistent WebSocket API Gateway WebSocket API ALB + ECS Managed connection management
High-frequency tight loop Lambda with Provisioned Concurrency ECS Fargate Remove cold start for latency-sensitive paths

Cost Model

Service Free Tier Pay-as-you-go
Lambda 1M requests/month, 400K GB-seconds $0.20/million requests + $0.0000166/GB-second
API Gateway HTTP API 1M calls/month first 12 months $1.00/million calls
Step Functions Express None $1.00/million state transitions
DynamoDB 25GB storage, 25 WCU, 25 RCU On-demand from $0.25/million reads

A typical serverless API handling 10M requests per month at 200ms average duration costs approximately $8–15 per month including DynamoDB.

Anti-Patterns to Avoid

⚠ 1. Monolithic Lambda Function

One Lambda function with a single handler routing all API endpoints internally with a router library (Express.js inside Lambda). The function grows to hundreds of lines, all code is deployed on every change, and a single cold start loads everything even for the simplest endpoint.

Hover to see the fix ↻
↺ Correct Approach

One Lambda function per logical operation or route group. Each function is small, independently deployable, and scales based on its own traffic pattern.

⚠ 2. Lambda Calling Lambda Synchronously

Function A invokes Function B via SDK and waits for the response before returning. If B is slow, A times out and the caller retries, amplifying load on B. Error handling becomes a chain of nested try-catch across function boundaries.

Hover to see the fix ↻
↺ Correct Approach

Use Step Functions to orchestrate multi-function workflows. Externalise state and error handling to the state machine rather than encoding it in Lambda code.

⚠ 3. Secrets in Environment Variables

Storing database passwords, API keys, and tokens as Lambda environment variables. They appear in plain text in the Lambda console, CloudTrail, and any log that captures environment state.

Hover to see the fix ↻
↺ Correct Approach

Store secrets in AWS Secrets Manager. Fetch at initialisation time outside the handler (warm cache), reference by ARN in the environment variable. Rotate automatically with Secrets Manager rotation.

Flowchart

%%{init:{'theme':'base','themeVariables':{'fontSize':'14px','fontFamily':'IBM Plex Sans, system-ui, sans-serif','primaryColor':'#DBEAFE','primaryTextColor':'#1e3a5f','primaryBorderColor':'#2563EB','lineColor':'#374151','clusterBkg':'#F9FAFB','clusterBorder':'#D1D5DB','edgeLabelBackground':'#FFFFFF'},'flowchart':{'curve':'orthogonal','padding':30,'nodeSpacing':65,'rankSpacing':75,'useMaxWidth':true}}}%% flowchart TD START([Client Sends HTTP Request]) START --> APIGW[API Gateway HTTP API\nLower cost than REST API\nCORS and throttling built-in] APIGW --> AUTH{Lambda Authoriser\nJWT valid?} AUTH -->|No| DENY[401 Unauthorised\nReturn to client] AUTH -->|Yes| ROUTE{Route to Handler} ROUTE -->|Simple operation| FN[Lambda Function\nArm64 Graviton2\nNode.js 20 or Python 3.12] ROUTE -->|Multi-step workflow| SFN[Step Functions\nExpress workflow\nState and error handling] FN --> COLD{Cold Start?} COLD -->|Yes, latency-sensitive| PC[Provisioned Concurrency\nPre-warmed instances] COLD -->|No| EXEC[Execute Handler\nFetch from DynamoDB or cache] PC --> EXEC SFN --> EXEC EXEC --> RESP[Return Response\nvia API Gateway] RESP --> DONE([Response Delivered to Client]) style START fill:#4f8ef7,color:#fff style DONE fill:#10b981,color:#fff style DENY fill:#fef3c7 style COLD fill:#fef3c7 style PC fill:#e0f2fe

References

  1. AWS — Lambda Developer Guide. docs.aws.amazon.com/lambda
  2. AWS — API Gateway HTTP API vs REST API comparison. docs.aws.amazon.com/apigateway
  3. AWS — Step Functions Developer Guide. docs.aws.amazon.com/step-functions
  4. AWS — Serverless Application Model (SAM). docs.aws.amazon.com/serverless-application-model
  5. Sbarski, Peter — Serverless Architectures on AWS. Manning, 2017.
Ascendion Engineering Knowledge Base ← Cloud