Introduction
Serverless computing eliminates infrastructure management, enabling developers to focus purely on code. AWS provides three core serverless services: AWS Lambda for event-driven compute, Amazon API Gateway for building APIs, and AWS Step Functions for orchestrating distributed workflows.
This guide explores serverless architecture patterns, Lambda execution models, API Gateway integration types, and Step Functions state machines. We'll cover cold start optimization, API caching strategies, and error handling patterns for production workloads.
AWS Developer Certification Series
📚 View Complete AWS Developer Certification Guide - Master all 7 parts with our comprehensive learning path.
This is Part IV (Current Article) of our comprehensive 7-part AWS developer guide:
- Part I: IAM EC2 & Auto Scaling
- Part II: RDS Aurora & DynamoDB
- Part III: SQS SNS & Kinesis
- Part IV: Lambda API Gateway (Current Article)
- Part V: ECS Fargate & IaC
- Part VI: Cognito KMS Security
- Part VII: CodePipeline & Monitoring
← Part III: SQS SNS & Kinesis | Part V: ECS Fargate & IaC →
AWS Lambda
Lambda Execution Model
Lambda runs code in response to events without provisioning servers, with automatic scaling and pay-per-use pricing.
Key Specifications:
- Memory: 128 MB to 10 GB (CPU scales with memory)
- Timeout: Maximum 15 minutes
- Deployment package: 50 MB (zipped), 250 MB (unzipped)
- Concurrency: 1,000 concurrent executions per region (soft limit)
- Pricing: $0.20 per 1M requests + $0.0000166667 per GB-second
Lambda Function Example
Node.js Lambda with DynamoDB:
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const {
DynamoDBDocumentClient,
GetCommand,
PutCommand,
} = require('@aws-sdk/lib-dynamodb');
// Initialize outside handler (reused across invocations)
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
exports.handler = async event => {
const userId = event.pathParameters.id;
try {
// Get item from DynamoDB
const response = await docClient.send(
new GetCommand({
TableName: process.env.TABLE_NAME,
Key: { userId },
}),
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(response.Item),
};
} catch (error) {
console.error('Error:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};
Invoke Lambda via AWS CLI:
# Synchronous invocation
aws lambda invoke \
--function-name my-function \
--payload '{"userId": "123"}' \
response.json
# Asynchronous invocation
aws lambda invoke \
--function-name my-function \
--invocation-type Event \
--payload '{"userId": "123"}' \
response.json
Lambda Invocation Types
Synchronous (Request-Response):
- API Gateway, ALB, Application Code
- Caller waits for response
- Retries: Client responsibility
- S3, SNS, EventBridge, CloudWatch Events
- Lambda queues internally, returns immediately
- Retries: 2 automatic retries with exponential backoff
- Configure DLQ (SQS/SNS) for failed events
Event Source Mapping (Polling):
- SQS, Kinesis, DynamoDB Streams
- Lambda polls source and invokes function
- Batch processing with configurable batch size
Cold Start Optimization
- Minimize package size - Remove unused dependencies
- Increase memory - More RAM = faster CPU and network
- Keep functions warm - Use Provisioned Concurrency for critical APIs
- Initialize outside handler - Reuse connections across invocations
- Use ARM (Graviton2) - 20% price reduction, 19% faster
// ❌ Bad - initializes every invocation
exports.handler = async event => {
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
// ... rest of code
};
// ✅ Good - reuses connection
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
exports.handler = async event => {
// Use existing dynamodb client
};
Amazon API Gateway
API Gateway Architecture
API Gateway creates RESTful and WebSocket APIs with built-in authorization, caching, and throttling.
API Types:
- REST API: Full features, caching, custom domains
- HTTP API: Lower cost (70% cheaper), lower latency, simpler
- WebSocket API: Bidirectional real-time communication
API Gateway Integration Types
Lambda Proxy Integration (AWS_PROXY):
- Request passes through to Lambda as-is
- Lambda must return specific response format
- Most common pattern
Lambda Custom Integration (AWS):
- Transform request/response using VTL (Velocity Template Language)
- More control over mapping
HTTP Proxy Integration:
- Forward to HTTP endpoint
AWS Service Integration:
- Direct integration with DynamoDB, S3, etc. (no Lambda needed)
Testing API Gateway with curl
# GET request
curl https://api.example.com/users/123
# POST with JSON body
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "John", "email": "john@example.com"}'
# With Authorization header
curl https://api.example.com/users \
-H "Authorization: Bearer eyJhbGc..."
# With API Key
curl https://api.example.com/users \
-H "x-api-key: abc123xyz"
# Invalidate cache for specific request
curl https://api.example.com/users \
-H "Cache-Control: max-age=0"
API Gateway Authorizers
Lambda Authorizer Example:
exports.handler = async event => {
const token = event.authorizationToken; // From Authorization header
const methodArn = event.methodArn;
// Validate token (JWT, API key, etc.)
const isValid = await validateToken(token);
if (isValid) {
return generatePolicy('user123', 'Allow', methodArn, {
userId: 'user123',
role: 'admin',
});
} else {
throw new Error('Unauthorized');
}
};
function generatePolicy(principalId, effect, resource, context = {}) {
return {
principalId,
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'execute-api:Invoke',
Effect: effect,
Resource: resource,
},
],
},
context, // Available in Lambda via event.requestContext.authorizer
};
}
API Gateway Caching
Enable caching per stage:
- Cache size: 0.5 GB to 237 GB
- TTL: 0 to 3600 seconds
- Per-method cache settings override stage settings
Cache key parameters:
- Query string parameters
- Request headers
- Path parameters
Invalidate cache:
- Manually via console/API
- Client request with
Cache-Control: max-age=0header (requires IAM permission)
AWS Step Functions
Step Functions State Machine
Step Functions orchestrates distributed workflows with visual state machines and built-in error handling.
State Types:
- Task: Invokes Lambda, ECS, Batch, or AWS service
- Choice: Conditional branching
- Parallel: Execute branches concurrently
- Wait: Delay for duration or until timestamp
- Map: Iterate over array
- Succeed/Fail: Terminal states
Step Functions Example
State Machine Definition:
{
"Comment": "Order processing workflow",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:validate",
"Next": "ProcessPayment",
"Catch": [
{
"ErrorEquals": ["ValidationError"],
"Next": "OrderFailed"
}
],
"Retry": [
{
"ErrorEquals": ["States.TaskFailed"],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2.0
}
]
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:payment",
"Next": "UpdateInventory"
},
"UpdateInventory": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:updateItem",
"Parameters": {
"TableName": "inventory",
"Key": {
"product_id": { "S.$": "$.productId" }
},
"UpdateExpression": "SET stock = stock - :qty",
"ExpressionAttributeValues": {
":qty": { "N.$": "$.quantity" }
}
},
"Next": "OrderComplete"
},
"OrderComplete": {
"Type": "Succeed"
},
"OrderFailed": {
"Type": "Fail",
"Error": "OrderValidationFailed"
}
}
}
Start Execution with AWS SDK (Node.js):
const { SFNClient, StartExecutionCommand } = require('@aws-sdk/client-sfn');
const client = new SFNClient({});
async function startWorkflow(orderId, productId, quantity) {
const command = new StartExecutionCommand({
stateMachineArn:
'arn:aws:states:us-east-1:123456789012:stateMachine:order-processing',
input: JSON.stringify({
orderId,
productId,
quantity,
}),
});
const response = await client.send(command);
console.log('Execution ARN:', response.executionArn);
return response.executionArn;
}
Production Best Practices
Lambda Best Practices
- Memory Allocation: Start with 512 MB, monitor CloudWatch metrics, adjust (more RAM = faster CPU)
- Cold Starts: Use Provisioned Concurrency for latency-sensitive APIs (costs more)
- Environment Variables: Store config, use AWS Systems Manager Parameter Store for secrets
- Timeout: Set appropriate timeout (default 3s, max 15 min)
- VPC: Only if accessing private resources (adds cold start overhead)
- Layers: Share dependencies across functions (max 5 layers per function)
API Gateway Best Practices
- Caching: Enable for GET requests with stable data (reduces Lambda invocations)
- Throttling: Set per-stage limits (default 10,000 requests/sec)
- Request Validation: Use JSON schema validation to reject malformed requests early
- API Keys: For partner/external API access control (use Usage Plans)
- Custom Domains: Use Route 53 + ACM for branded APIs
Step Functions Best Practices
- Error Handling: Always configure Retry and Catch blocks
- Timeouts: Set appropriate timeouts for each state (prevents stuck executions)
- Standard vs Express:
- Standard: Long-running (max 1 year), auditable, exactly-once execution
- Express: High-volume, short-duration (max 5 min), at-least-once
- Cost Optimization: Use Express for high-throughput workloads (60% cheaper)
Monitoring and Troubleshooting
Key Metrics:
Lambda:
Invocations- Number of times invokedErrors- Failed invocationsDuration- Execution timeThrottles- Requests throttled due to concurrency limitsConcurrentExecutions- Number of running instances
API Gateway:
Count- Total API requests4XXError/5XXError- Client/server errorsLatency- Request processing timeCacheHitCount/CacheMissCount- Cache performance
Step Functions:
ExecutionsFailed- Failed executionsExecutionTime- Workflow durationExecutionThrottled- Throttled executions
Exam Tips
Key Concepts:
- Lambda timeout maximum is 15 minutes (use Step Functions for longer workflows)
- Lambda@Edge runs at CloudFront edge locations (max 128 MB memory, 5s timeout)
- API Gateway cache sizes: 0.5 GB to 237 GB, TTL 0 to 3600s
- Step Functions Express max 5 minutes, Standard max 1 year
- Lambda Provisioned Concurrency keeps instances warm (reduces cold starts)
Common Scenarios:
- "Need to run code for 30 minutes" → Step Functions + Lambda (chain functions)
- "Reduce API latency" → API Gateway caching + Provisioned Concurrency
- "Lambda cold starts" → Increase memory, use Provisioned Concurrency
- "Orchestrate microservices" → Step Functions
- "Real-time bidirectional communication" → API Gateway WebSocket
- "Process 10,000 items in parallel" → Step Functions Map state
Frequently Asked Questions
Q: What is AWS Lambda and how does it work?
AWS Lambda is a serverless compute service that runs code in response to events without provisioning servers. You upload code, configure triggers like API calls or S3 uploads, and Lambda automatically scales execution. You pay only for compute time consumed, measured in milliseconds. Maximum execution time is 15 minutes.
Q: How do I reduce Lambda cold start latency?
Reduce cold starts by increasing memory allocation (faster CPU), using Provisioned Concurrency to keep instances warm, minimizing deployment package size, avoiding VPC unless necessary, and choosing interpreted languages like Python or Node.js. Provisioned Concurrency costs more but eliminates cold starts for critical APIs.
Q: What is the difference between API Gateway REST API and HTTP API?
REST API offers advanced features like request validation, caching, usage plans, and API keys with higher cost. HTTP API is simpler, 70% cheaper, faster, and supports OIDC/JWT authorization natively. Use HTTP API for cost-sensitive modern applications, REST API when you need caching or throttling.
Q: When should I use Step Functions instead of Lambda?
Use Step Functions when orchestrating multiple services, requiring workflows longer than 15 minutes, needing visual workflow tracking, or implementing complex error handling with retries. Lambda alone works for simple event processing. Step Functions coordinates Lambda functions, ECS tasks, and AWS services into reliable workflows.
Q: How does Lambda concurrency work?
Lambda concurrency is the number of function instances running simultaneously. AWS provides 1,000 concurrent executions per region by default (can be increased). When exceeded, requests are throttled. Reserved concurrency guarantees capacity for critical functions. Monitor ConcurrentExecutions metric to avoid throttling.
Q: What are API Gateway stages and how are they used?
API Gateway stages represent different deployment environments like dev, test, and production for the same API. Each stage has independent settings for caching, throttling, and logging. Promote APIs through stages using stage variables and canary deployments for gradual traffic shifting.
Q: How do Step Functions handle errors and retries?
Step Functions use Retry and Catch blocks in state definitions. Retry specifies automatic retry attempts with exponential backoff for transient errors. Catch handles specific error types and transitions to fallback states. This provides robust error handling without writing complex retry logic in application code.
Conclusion
AWS serverless services enable building scalable applications without managing infrastructure. Lambda provides event-driven compute with automatic scaling, API Gateway creates secure APIs with built-in features, and Step Functions orchestrates complex workflows with visual state machines.
Choose Lambda for event-driven processing, API Gateway for RESTful and WebSocket APIs, and Step Functions for coordinating distributed services. Understanding execution models, integration patterns, and optimization techniques is essential for both production serverless architectures and the AWS Certified Developer Associate exam.