| 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 = response.!.()
(body, key)
.()
}
}
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.) {
order = .(record.)
{
(order)
} (err) {
.(, err)
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}`,
: { : }
})
}
{ }
(): <> {
items = [
{
: {
: process..!,
: {
: ,
: ,
: ,
: ,
...order
}
}
},
{
: {
: process..!,
: {
: ,
: ,
: order.,
: order.,
: order.
}
}
}
]
client.( ({ : 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