AWS Lambda Skill
aws-lambda
Build serverless applications with AWS Lambda and TypeScript. Covers handler patterns, API Gateway integration, DynamoDB operations, SQS/S3/DynamoDB Streams event sources, SAM templates, and CDK infrastructure. Use for serverless APIs, event-driven architectures, and AWS backend development.
SKILL.md
Full skill instructions
AWS Lambda Skill
Expert guidance for AWS Lambda with TypeScript, API Gateway, DynamoDB, and serverless patterns.
Triggers
Use this skill when you see:
- aws lambda, lambda function, serverless aws
- api gateway, sam template, cdk lambda
- dynamodb streams, sqs handler, s3 trigger
- lambda handler, cold start, provisioned concurrency
Instructions
Lambda Handler Patterns
Basic Handler with TypeScript
import type {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context
} from 'aws-lambda';
export const handler = async (
event: APIGatewayProxyEvent,
context: Context
): Promise<APIGatewayProxyResult> => {
try {
const body = JSON.parse(event.body ?? '{}');
// Your logic here
const result = await processRequest(body);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(result),
};
} catch (error) {
console.error('Error:', error);
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Internal Server Error' }),
};
}
};
Middleware Pattern with Middy
import middy from '@middy/core';
import jsonBodyParser from '@middy/http-json-body-parser';
import httpErrorHandler from '@middy/http-error-handler';
import cors from '@middy/http-cors';
import validator from '@middy/validator';
import { transpileSchema } from '@middy/validator/transpile';
const inputSchema = {
type: 'object',
properties: {
body: {
type: 'object',
properties: {
email: { type: 'string', format: 'email' },
name: { type: 'string', minLength: 1 },
},
required: ['email', 'name'],
},
},
};
const baseHandler = async (event: APIGatewayProxyEvent) => {
const { email, name } = event.body as { email: string; name: string };
const user = await createUser({ email, name });
return {
statusCode: 201,
body: JSON.stringify(user),
};
};
export const handler = middy(baseHandler)
.use(jsonBodyParser())
.use(validator({ eventSchema: transpileSchema(inputSchema) }))
.use(httpErrorHandler())
.use(cors());
Event Source Handlers
SQS Handler
import type { SQSEvent, SQSHandler } from 'aws-lambda';
export const sqsHandler: SQSHandler = async (event: SQSEvent) => {
const results = await Promise.allSettled(
event.Records.map(async (record) => {
const message = JSON.parse(record.body);
await processMessage(message);
})
);
// Return batch item failures for partial batch response
const failures = results
.map((result, index) =>
result.status === 'rejected'
? { itemIdentifier: event.Records[index].messageId }
: null
)
.filter(Boolean);
return { batchItemFailures: failures };
};
DynamoDB Streams Handler
import type { DynamoDBStreamEvent, DynamoDBStreamHandler } from 'aws-lambda';
import { unmarshall } from '@aws-sdk/util-dynamodb';
export const streamHandler: DynamoDBStreamHandler = async (
event: DynamoDBStreamEvent
) => {
for (const record of event.Records) {
if (record.eventName === 'INSERT' && record.dynamodb?.NewImage) {
const item = unmarshall(record.dynamodb.NewImage);
await handleNewItem(item);
}
}
};
S3 Handler
import type { S3Event, S3Handler } from 'aws-lambda';
export const s3Handler: S3Handler = async (event: S3Event) => {
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '));
await processS3Object(bucket, key);
}
};
DynamoDB Integration
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
DynamoDBDocumentClient,
GetCommand,
PutCommand,
QueryCommand,
UpdateCommand,
} from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.TABLE_NAME!;
// Get item
async function getUser(userId: string) {
const result = await docClient.send(new GetCommand({
TableName: TABLE_NAME,
Key: { PK: `USER#${userId}`, SK: `USER#${userId}` },
}));
return result.Item;
}
// Put item
async function createUser(user: User) {
await docClient.send(new PutCommand({
TableName: TABLE_NAME,
Item: {
PK: `USER#${user.id}`,
SK: `USER#${user.id}`,
...user,
createdAt: new Date().toISOString(),
},
ConditionExpression: 'attribute_not_exists(PK)',
}));
}
// Query with GSI
async function getUserOrders(userId: string) {
const result = await docClient.send(new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk AND begins_with(GSI1SK, :sk)',
ExpressionAttributeValues: {
':pk': `USER#${userId}`,
':sk': 'ORDER#',
},
}));
return result.Items ?? [];
}
SAM Template
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Timeout: 30
Runtime: nodejs20.x
MemorySize: 256
Architectures:
- arm64
Environment:
Variables:
TABLE_NAME: !Ref UsersTable
Resources:
ApiFunction:
Type: AWS::Serverless::Function
Properties:
Handler: dist/handlers/api.handler
Events:
GetUsers:
Type: Api
Properties:
Path: /users
Method: GET
CreateUser:
Type: Api
Properties:
Path: /users
Method: POST
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Metadata:
BuildMethod: esbuild
BuildProperties:
Minify: true
Target: es2022
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: PK
AttributeType: S
- AttributeName: SK
AttributeType: S
KeySchema:
- AttributeName: PK
KeyType: HASH
- AttributeName: SK
KeyType: RANGE
Outputs:
ApiUrl:
Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod'
CDK Lambda
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
export class ApiStack extends cdk.Stack {
constructor(scope: cdk.App, id: string) {
super(scope, id);
const table = new dynamodb.Table(this, 'UsersTable', {
partitionKey: { name: 'PK', type: dynamodb.AttributeType.STRING },
sortKey: { name: 'SK', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
});
const fn = new NodejsFunction(this, 'ApiHandler', {
entry: 'src/handlers/api.ts',
runtime: lambda.Runtime.NODEJS_20_X,
architecture: lambda.Architecture.ARM_64,
environment: {
TABLE_NAME: table.tableName,
},
bundling: {
minify: true,
},
});
table.grantReadWriteData(fn);
new apigateway.LambdaRestApi(this, 'Api', {
handler: fn,
});
}
}
Best Practices
| Practice | Implementation |
|---|---|
| Cold starts | Use ARM64, minimize dependencies, use provisioned concurrency for critical paths |
| Connections | Reuse SDK clients outside handler, use connection pooling |
| Error handling | Use structured error responses, implement retries with backoff |
| Logging | Use structured JSON logging, include request IDs |
| Security | Use IAM roles, validate input, encrypt sensitive data |
Common Workflows
New Lambda Function
- Create handler file with TypeScript types
- Add middleware for validation/error handling
- Configure SAM/CDK template
- Set up IAM permissions
- Deploy and test
Event-Driven Architecture
- Define event source (SQS, S3, DynamoDB Streams)
- Create event handler with proper typing
- Implement batch failure handling
- Configure DLQ for failed events
- Monitor with CloudWatch
