AgentSkillsCN

Aws Lambda

Aws Lambda

SKILL.md

AWS Lambda Skill

Expert guidance for AWS Lambda with TypeScript, API Gateway, DynamoDB, and serverless patterns.

Triggers

  • aws lambda, lambda, serverless aws, api gateway, sam, cdk lambda

Lambda Handler Patterns

Basic Handler with TypeScript

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

typescript
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

typescript
// 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

typescript
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 ?? [];
}

// Update item
async function updateUserEmail(userId: string, email: string) {
  await docClient.send(new UpdateCommand({
    TableName: TABLE_NAME,
    Key: { PK: `USER#${userId}`, SK: `USER#${userId}` },
    UpdateExpression: 'SET email = :email, updatedAt = :now',
    ExpressionAttributeValues: {
      ':email': email,
      ':now': new Date().toISOString(),
    },
  }));
}

SAM Template

yaml
# 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

typescript
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

PracticeImplementation
Cold startsUse ARM64, minimize dependencies, use provisioned concurrency for critical paths
ConnectionsReuse SDK clients outside handler, use connection pooling
Error handlingUse structured error responses, implement retries with backoff
LoggingUse structured JSON logging, include request IDs
SecurityUse IAM roles, validate input, encrypt sensitive data