| name | aws-patterns |
| description | Lambda best practices, S3 event patterns, SQS/SNS fanout, and DynamoDB access patterns for serverless AWS architectures. |
AWS Patterns
Serverless and managed service patterns for AWS production workloads.
Lambda Best Practices
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb'
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}))
export const handler = async (event: APIGatewayProxyEvent) => {
try {
const userId = event.pathParameters?.id
if (!userId) {
return { statusCode: 400, body: JSON.stringify({ error: 'Missing user ID' }) }
}
const result = await client.send(new GetCommand({
TableName: process.env.USERS_TABLE!,
Key: { pk: `USER#${userId}`, sk: `PROFILE` }
}))
if (!result.Item) {
return { statusCode: 404, body: JSON.stringify({ error: 'User not found' }) }
}
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(result.Item)
}
} catch (err) {
console.error('Handler error:', err)
return { statusCode: 500, body: JSON.stringify({ error: 'Internal server error' }) }
}
}
S3 Event Processing
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3'
const s3 = new S3Client({})
export const handler = async (event: S3Event) => {
for (const record of event.Records) {
const bucket = record.s3.bucket.name
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '))
const size = record.s3.object.size
if (size > 10_000_000) {
console.warn(`Skipping oversized file: ${key} (${size} bytes)`)
continue
}
const response = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }))
const body = await response.Body!.transformToByteArray()
await processImage(body, key)
console.log(`Processed ${key} (${size} bytes)`)
}
}
SQS/SNS Fanout Pattern
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'
const sns = new SNSClient({})
async function publishOrderEvent(order: Order): Promise<void> {
await sns.send(new PublishCommand({
TopicArn: process.env.ORDER_EVENTS_TOPIC!,
Message: JSON.stringify(order),
MessageAttributes: {
eventType: { DataType: 'String', StringValue: 'order.created' },
region: { DataType: 'String', StringValue: order.region }
}
}))
}
export const emailHandler = async (event: SQSEvent) => {
for (const record of event.Records) {
const order = JSON.parse(record.body) as Order
try {
await sendOrderConfirmation(order)
} catch (err) {
console.error(`Failed to send email for order ${order.id}:`, err)
throw err
}
}
}
DynamoDB Single-Table Design
const AccessPatterns = {
getUserProfile: (userId: string) => ({
pk: `USER#${userId}`,
sk: `PROFILE`
}),
getUserOrders: (userId: string) => ({
pk: `USER#${userId}`,
sk: { begins_with: 'ORDER#' }
}),
getOrderWithItems: (orderId: string) => ({
pk: `ORDER#${orderId}`,
sk: { begins_with: '' }
}),
getOrdersByStatus: (status: string) => ({
GSI1PK: `STATUS#${status}`,
GSI1SK: { begins_with: '' }
})
}
import { TransactWriteCommand } from '@aws-sdk/lib-dynamodb'
async function createOrder(order: Order): Promise<void> {
const items = [
{
Put: {
TableName: process.env.TABLE!,
Item: {
pk: `ORDER#${order.id}`,
sk: 'METADATA',
GSI1PK: `STATUS#${order.status}`,
GSI1SK: `${order.createdAt}#${order.id}`,
...order
}
}
},
{
Put: {
TableName: process.env.TABLE!,
Item: {
pk: `USER#${order.userId}`,
sk: `ORDER#${order.createdAt}#${order.id}`,
orderId: order.id,
total: order.total,
status: order.status
}
}
}
]
await client.send(new TransactWriteCommand({ TransactItems: items }))
}
Checklist
Anti-Patterns
- Initializing SDK clients inside Lambda handler (cold start penalty every time)
- Synchronous Lambda chains: A calls B calls C (use Step Functions)
- DynamoDB scan operations in production (always query with pk/sk)
- S3 event without idempotency: Lambda can be invoked multiple times per event
- Oversized Lambda packages (>50MB): use layers or container images
- Missing DLQ on SQS: failed messages silently disappear after retention period