Production-ready serverless applications with AWS Lambda including API Gateway, DynamoDB, and event processing
# AWS Lambda Serverless Patterns for Google Antigravity
Build scalable serverless applications with AWS Lambda using Google Antigravity's Gemini 3 engine. This guide covers function handlers, API Gateway integration, DynamoDB operations, and event processing.
## Lambda Handler Patterns
```typescript
// src/handlers/api.ts
import {
APIGatewayProxyHandlerV2,
APIGatewayProxyEventV2,
APIGatewayProxyResultV2,
} from 'aws-lambda';
import { z } from 'zod';
// Middleware wrapper for common functionality
type Handler<T = any> = (
event: APIGatewayProxyEventV2,
body: T
) => Promise<APIGatewayProxyResultV2>;
function withMiddleware<T>(
schema: z.ZodType<T>,
handler: Handler<T>
): APIGatewayProxyHandlerV2 {
return async (event) => {
try {
// Parse and validate body
const body = event.body ? JSON.parse(event.body) : {};
const validatedBody = schema.parse(body);
// Call handler
return await handler(event, validatedBody);
} catch (error) {
if (error instanceof z.ZodError) {
return {
statusCode: 400,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: 'Validation error',
details: error.errors,
}),
};
}
console.error('Handler error:', error);
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Internal server error' }),
};
}
};
}
// Response helper
function jsonResponse(
statusCode: number,
body: Record<string, any>
): APIGatewayProxyResultV2 {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify(body),
};
}
// User creation handler
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1),
});
export const createUser = withMiddleware(
CreateUserSchema,
async (event, body) => {
const { email, name } = body;
// Create user in database
const user = await userService.create({ email, name });
return jsonResponse(201, { user });
}
);
// Get user handler
export const getUser: APIGatewayProxyHandlerV2 = async (event) => {
const userId = event.pathParameters?.id;
if (!userId) {
return jsonResponse(400, { error: 'User ID required' });
}
const user = await userService.getById(userId);
if (!user) {
return jsonResponse(404, { error: 'User not found' });
}
return jsonResponse(200, { user });
};
```
## DynamoDB Operations
```typescript
// src/services/dynamodb.ts
import {
DynamoDBClient,
QueryCommand,
PutItemCommand,
UpdateItemCommand,
DeleteItemCommand,
GetItemCommand,
} from '@aws-sdk/client-dynamodb';
import {
DynamoDBDocumentClient,
QueryCommandInput,
PutCommand,
GetCommand,
UpdateCommand,
DeleteCommand,
} from '@aws-sdk/lib-dynamodb';
import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client, {
marshallOptions: { removeUndefinedValues: true },
});
const TABLE_NAME = process.env.TABLE_NAME!;
interface User {
pk: string;
sk: string;
id: string;
email: string;
name: string;
createdAt: string;
updatedAt: string;
}
export const userService = {
async create(data: { email: string; name: string }): Promise<User> {
const id = crypto.randomUUID();
const now = new Date().toISOString();
const user: User = {
pk: `USER#${id}`,
sk: `PROFILE#${id}`,
id,
email: data.email,
name: data.name,
createdAt: now,
updatedAt: now,
};
await docClient.send(
new PutCommand({
TableName: TABLE_NAME,
Item: user,
ConditionExpression: 'attribute_not_exists(pk)',
})
);
// Create email index entry
await docClient.send(
new PutCommand({
TableName: TABLE_NAME,
Item: {
pk: `EMAIL#${data.email}`,
sk: `USER#${id}`,
userId: id,
},
})
);
return user;
},
async getById(id: string): Promise<User | null> {
const result = await docClient.send(
new GetCommand({
TableName: TABLE_NAME,
Key: {
pk: `USER#${id}`,
sk: `PROFILE#${id}`,
},
})
);
return (result.Item as User) || null;
},
async getByEmail(email: string): Promise<User | null> {
const indexResult = await docClient.send(
new GetCommand({
TableName: TABLE_NAME,
Key: {
pk: `EMAIL#${email}`,
sk: `EMAIL#${email}`,
},
})
);
if (!indexResult.Item) return null;
return this.getById(indexResult.Item.userId as string);
},
async update(id: string, data: Partial<User>): Promise<User> {
const updateExpressions: string[] = [];
const expressionNames: Record<string, string> = {};
const expressionValues: Record<string, any> = {};
Object.entries(data).forEach(([key, value]) => {
if (value !== undefined && !['pk', 'sk', 'id'].includes(key)) {
updateExpressions.push(`#${key} = :${key}`);
expressionNames[`#${key}`] = key;
expressionValues[`:${key}`] = value;
}
});
// Always update updatedAt
updateExpressions.push('#updatedAt = :updatedAt');
expressionNames['#updatedAt'] = 'updatedAt';
expressionValues[':updatedAt'] = new Date().toISOString();
const result = await docClient.send(
new UpdateCommand({
TableName: TABLE_NAME,
Key: {
pk: `USER#${id}`,
sk: `PROFILE#${id}`,
},
UpdateExpression: `SET ${updateExpressions.join(', ')}``,
ExpressionAttributeNames: expressionNames,
ExpressionAttributeValues: expressionValues,
ReturnValues: 'ALL_NEW',
})
);
return result.Attributes as User;
},
async delete(id: string): Promise<void> {
await docClient.send(
new DeleteCommand({
TableName: TABLE_NAME,
Key: {
pk: `USER#${id}`,
sk: `PROFILE#${id}`,
},
})
);
},
async list(limit = 20, lastKey?: Record<string, any>): Promise<{
users: User[];
lastKey?: Record<string, any>;
}> {
const result = await docClient.send(
new QueryCommand({
TableName: TABLE_NAME,
IndexName: 'GSI1',
KeyConditionExpression: 'gsi1pk = :pk',
ExpressionAttributeValues: marshall({ ':pk': 'USER' }),
Limit: limit,
ExclusiveStartKey: lastKey ? marshall(lastKey) : undefined,
})
);
return {
users: (result.Items || []).map((item) => unmarshall(item) as User),
lastKey: result.LastEvaluatedKey
? unmarshall(result.LastEvaluatedKey)
: undefined,
};
},
};
```
## Event Processing
```typescript
// src/handlers/events.ts
import { SQSHandler, SQSEvent, SQSRecord } from 'aws-lambda';
import { S3Event, S3Handler } from 'aws-lambda';
import { EventBridgeHandler } from 'aws-lambda';
// SQS Queue Handler
export const processSQSMessages: SQSHandler = async (event: SQSEvent) => {
const results = await Promise.allSettled(
event.Records.map(processRecord)
);
// Return failed message IDs for retry
const failures = results
.map((result, index) => ({
result,
messageId: event.Records[index].messageId,
}))
.filter(({ result }) => result.status === 'rejected')
.map(({ messageId }) => ({ itemIdentifier: messageId }));
return { batchItemFailures: failures };
};
async function processRecord(record: SQSRecord): Promise<void> {
const body = JSON.parse(record.body);
console.log('Processing message:', body);
// Process the message
switch (body.type) {
case 'USER_CREATED':
await handleUserCreated(body.data);
break;
case 'ORDER_PLACED':
await handleOrderPlaced(body.data);
break;
default:
console.warn('Unknown message type:', body.type);
}
}
// S3 Event Handler
export const processS3Upload: 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, ' '));
console.log(`Processing file: ${bucket}/${key}`);
// Process uploaded file
await processUploadedFile(bucket, key);
}
};
// EventBridge Handler
interface OrderEvent {
orderId: string;
userId: string;
total: number;
}
export const handleOrderEvent: EventBridgeHandler<'OrderCreated', OrderEvent, void> =
async (event) => {
const { orderId, userId, total } = event.detail;
console.log(`Processing order ${orderId} for user ${userId}`);
// Send confirmation email
await sendOrderConfirmation(userId, orderId);
// Update analytics
await updateAnalytics({ orderId, total });
};
```
## Best Practices
Google Antigravity's Gemini 3 engine recommends these Lambda patterns: Use connection pooling for database connections. Implement proper error handling with retries. Minimize cold start times with smaller bundles. Use environment variables for configuration. Implement structured logging for observability.This AWS Lambda prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their aws lambda implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For AWS Lambda projects, consider mentioning your framework version, coding style, and any specific libraries you're using.