Code Quality
- Follow consistent naming conventions
- Implement proper error handling
- Use TypeScript for better type safety
- Keep functions focused and small
Security & Performance
- Validate all input data
- Use environment variables for secrets
- Implement proper authentication
- Optimize for cold start performance
Request Function Patterns
HTTP Method Handling
export const handler = async (event) => {
const { httpMethod, body } = event;
const data = body ? JSON.parse(body) : {};
switch (httpMethod) {
case 'GET':
return {
statusCode: 200,
body: JSON.stringify({ users: await getUsers() })
};
case 'POST':
const newUser = await createUser(data);
return {
statusCode: 201,
body: JSON.stringify({ id: newUser.id, message: 'User created' })
};
case 'PUT':
const updatedUser = await updateUser(data);
return {
statusCode: 200,
body: JSON.stringify(updatedUser)
};
case 'DELETE':
await deleteUser(data.id);
return {
statusCode: 204,
body: ''
};
default:
return {
statusCode: 405,
body: JSON.stringify({ error: 'Method not allowed' })
};
}
};
{
"httpMethod": "POST",
"body": "{\"name\":\"John Doe\",\"email\":\"john@example.com\"}"
}
{
"statusCode": 201,
"body": "{\"id\":\"user_123\",\"message\":\"User created\"}"
}
Input Validation
export const handler = async (event) => {
const { body } = event;
try {
const data = JSON.parse(body || '{}');
// Validate required fields
const errors = [];
if (!data.email) errors.push('Email is required');
if (!data.password) errors.push('Password is required');
if (data.password && data.password.length < 8) {
errors.push('Password must be at least 8 characters');
}
// Return validation errors
if (errors.length > 0) {
return {
statusCode: 400,
body: JSON.stringify({
error: 'Validation failed',
details: errors
})
};
}
// Process valid data
const result = await processUser(data);
return {
statusCode: 200,
body: JSON.stringify(result)
};
} catch (error) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Invalid JSON' })
};
}
};
{
"email": "",
"password": "123"
}
{
"statusCode": 400,
"body": {
"error": "Validation failed",
"details": [
"Email is required",
"Password must be at least 8 characters"
]
}
}
Error Handling
export const handler = async (event) => {
try {
const result = await processRequest(event);
return {
statusCode: 200,
body: JSON.stringify({ success: true, data: result })
};
} catch (error) {
console.error('Request processing error:', error);
// Handle validation errors
if (error.name === 'ValidationError') {
return {
statusCode: 400,
body: JSON.stringify({
error: 'Invalid input',
details: error.message
})
};
}
// Handle not found errors
if (error.name === 'NotFoundError') {
return {
statusCode: 404,
body: JSON.stringify({ error: 'Resource not found' })
};
}
// Handle unexpected errors
return {
statusCode: 500,
body: JSON.stringify({
error: 'Internal server error',
requestId: event.requestContext?.requestId
})
};
}
};
{
"ValidationError": "400 - Bad Request",
"NotFoundError": "404 - Not Found",
"AuthenticationError": "401 - Unauthorized",
"PermissionError": "403 - Forbidden",
"RateLimitError": "429 - Too Many Requests",
"UnexpectedError": "500 - Internal Server Error"
}
{
"error": "Error description",
"details": "Additional error details",
"requestId": "req_123456789",
"timestamp": "2024-01-12T10:30:45.123Z"
}
Security Best Practices
Authentication & Authorization
import jwt from 'jsonwebtoken';
export const handler = async (event) => {
try {
// Extract token from header
const authHeader = event.headers.Authorization || event.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return {
statusCode: 401,
body: JSON.stringify({ error: 'Missing or invalid authorization header' })
};
}
const token = authHeader.substring(7);
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Check user permissions
if (!decoded.permissions?.includes('admin')) {
return {
statusCode: 403,
body: JSON.stringify({ error: 'Insufficient permissions' })
};
}
// Continue with authenticated request
const result = await processAuthenticatedRequest(decoded, event);
return {
statusCode: 200,
body: JSON.stringify(result)
};
} catch (error) {
return {
statusCode: 401,
body: JSON.stringify({ error: 'Invalid token' })
};
}
};
import validator from 'validator';
export const sanitizeInput = (data) => {
return {
email: validator.isEmail(data.email) ? validator.normalizeEmail(data.email) : null,
name: validator.escape(data.name || '').trim(),
phone: validator.isMobilePhone(data.phone) ? data.phone : null,
url: validator.isURL(data.url) ? data.url : null
};
};
export const handler = async (event) => {
const rawData = JSON.parse(event.body || '{}');
const sanitizedData = sanitizeInput(rawData);
// Validate sanitized data
if (!sanitizedData.email) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Valid email is required' })
};
}
// Process with clean data
const result = await processUser(sanitizedData);
return {
statusCode: 200,
body: JSON.stringify(result)
};
};
Environment Variables & Secrets
// Use environment variables for sensitive data
const config = {
database: {
host: process.env.DB_HOST,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
ssl: process.env.NODE_ENV === 'production'
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
},
external: {
apiKey: process.env.EXTERNAL_API_KEY,
webhookSecret: process.env.WEBHOOK_SECRET
}
};
// Validate required environment variables
const requiredEnvVars = ['DB_HOST', 'JWT_SECRET', 'EXTERNAL_API_KEY'];
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
if (missingVars.length > 0) {
throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`);
}
const rateLimiter = new Map();
export const checkRateLimit = (clientId: string, limit = 100, windowMs = 60000) => {
const now = Date.now();
const clientData = rateLimiter.get(clientId) || { requests: 0, resetTime: now + windowMs };
// Reset if window expired
if (now > clientData.resetTime) {
clientData.requests = 0;
clientData.resetTime = now + windowMs;
}
clientData.requests++;
rateLimiter.set(clientId, clientData);
return {
allowed: clientData.requests <= limit,
remaining: Math.max(0, limit - clientData.requests),
resetTime: clientData.resetTime
};
};
export const handler = async (event) => {
const clientId = event.requestContext.identity.sourceIp;
const rateLimit = checkRateLimit(clientId);
if (!rateLimit.allowed) {
return {
statusCode: 429,
headers: {
'X-RateLimit-Remaining': rateLimit.remaining.toString(),
'X-RateLimit-Reset': rateLimit.resetTime.toString()
},
body: JSON.stringify({ error: 'Rate limit exceeded' })
};
}
// Process request
const result = await processRequest(event);
return {
statusCode: 200,
headers: {
'X-RateLimit-Remaining': rateLimit.remaining.toString()
},
body: JSON.stringify(result)
};
};
Performance Optimization
Cold Start Optimization
// Initialize outside handler for reuse across invocations
import { createConnection } from './database';
import { initializeCache } from './cache';
let dbConnection;
let cache;
// Initialize on first import
const initializeResources = async () => {
if (!dbConnection) {
dbConnection = await createConnection();
}
if (!cache) {
cache = await initializeCache();
}
};
export const handler = async (event) => {
// Ensure resources are initialized
await initializeResources();
// Use cached resources
const result = await dbConnection.query('SELECT * FROM users');
await cache.set('users', result, 300); // Cache for 5 minutes
return {
statusCode: 200,
body: JSON.stringify(result)
};
};
const responseCache = new Map();
export const handler = async (event) => {
const cacheKey = `${event.httpMethod}:${event.path}:${JSON.stringify(event.queryStringParameters)}`;
const cachedResponse = responseCache.get(cacheKey);
// Return cached response if available and not expired
if (cachedResponse && cachedResponse.expiresAt > Date.now()) {
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'X-Cache': 'HIT'
},
body: JSON.stringify(cachedResponse.data)
};
}
// Process request and cache result
const result = await processRequest(event);
responseCache.set(cacheKey, {
data: result,
expiresAt: Date.now() + (5 * 60 * 1000) // 5 minutes
});
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'X-Cache': 'MISS'
},
body: JSON.stringify(result)
};
};
Database Optimization
import { Pool } from 'pg';
// Create connection pool outside handler
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
max: 10, // Maximum pool size
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
export const handler = async (event) => {
const client = await pool.connect();
try {
// Use parameterized queries to prevent SQL injection
const result = await client.query(
'SELECT * FROM users WHERE email = $1 AND status = $2',
[event.body.email, 'active']
);
return {
statusCode: 200,
body: JSON.stringify(result.rows)
};
} finally {
client.release(); // Always release the client back to pool
}
};
export const handler = async (event) => {
const { operations } = JSON.parse(event.body);
// Process operations in batches to avoid overwhelming the database
const batchSize = 10;
const results = [];
for (let i = 0; i < operations.length; i += batchSize) {
const batch = operations.slice(i, i + batchSize);
// Process batch concurrently
const batchPromises = batch.map(async (operation) => {
try {
return await processOperation(operation);
} catch (error) {
return { error: error.message, operation: operation.id };
}
});
const batchResults = await Promise.allSettled(batchPromises);
results.push(...batchResults.map(result =>
result.status === 'fulfilled' ? result.value : result.reason
));
}
return {
statusCode: 200,
body: JSON.stringify({
processed: results.length,
results
})
};
};
Testing Best Practices
Unit Testing
import { handler } from '../src/user-handler';
describe('User Handler', () => {
test('should create user with valid data', async () => {
// Arrange
const event = {
httpMethod: 'POST',
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com',
password: 'securePassword123'
})
};
// Act
const response = await handler(event);
const body = JSON.parse(response.body);
// Assert
expect(response.statusCode).toBe(201);
expect(body).toHaveProperty('id');
expect(body.message).toBe('User created');
});
test('should return validation error for invalid email', async () => {
// Arrange
const event = {
httpMethod: 'POST',
body: JSON.stringify({
name: 'John Doe',
email: 'invalid-email',
password: 'securePassword123'
})
};
// Act
const response = await handler(event);
const body = JSON.parse(response.body);
// Assert
expect(response.statusCode).toBe(400);
expect(body.error).toBe('Validation failed');
expect(body.details).toContain('Valid email is required');
});
});
import { jest } from '@jest/globals';
import { handler } from '../src/payment-handler';
// Mock external payment service
jest.mock('../src/services/payment-service', () => ({
processPayment: jest.fn(),
refundPayment: jest.fn()
}));
import { processPayment } from '../src/services/payment-service';
describe('Payment Handler', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should process payment successfully', async () => {
// Arrange
(processPayment as jest.Mock).mockResolvedValue({
id: 'pay_123',
status: 'succeeded',
amount: 1999
});
const event = {
body: JSON.stringify({
amount: 1999,
currency: 'usd',
paymentMethod: 'card_123'
})
};
// Act
const response = await handler(event);
const body = JSON.parse(response.body);
// Assert
expect(response.statusCode).toBe(200);
expect(body.paymentId).toBe('pay_123');
expect(processPayment).toHaveBeenCalledWith({
amount: 1999,
currency: 'usd',
paymentMethod: 'card_123'
});
});
});
Response Function Patterns
Conditional Enhancement
export const handler = async (event) => {
const { request, response } = event;
const responseBody = JSON.parse(response.body);
// Only enhance specific endpoints
if (request.path.includes('/api/')) {
// Add API-specific enhancements
return {
...response,
headers: {
...response.headers,
'X-API-Version': '1.0',
'X-Rate-Limit': '1000'
},
body: JSON.stringify({
...responseBody,
meta: {
version: '1.0',
timestamp: new Date().toISOString()
}
})
};
}
return response; // Return unchanged for other endpoints
};
export const handler = async (event) => {
const { request, response } = event;
// Enhance error responses with helpful information
if (response.statusCode >= 400) {
const errorBody = JSON.parse(response.body);
return {
...response,
headers: {
...response.headers,
'X-Error-ID': generateErrorId(),
'X-Support-Contact': 'support@myapp.com'
},
body: JSON.stringify({
...errorBody,
support: {
documentation: 'https://docs.myapp.com',
contact: 'support@myapp.com',
statusPage: 'https://status.myapp.com'
},
requestId: request.requestContext?.requestId
})
};
}
return response;
};
export const handler = async (event) => {
const { request, response } = event;
const responseBody = JSON.parse(response.body);
// Add performance optimizations
const optimizedResponse = {
...response,
headers: {
...response.headers,
// Compression
'Content-Encoding': 'gzip',
// Caching based on content type
'Cache-Control': request.path.includes('/static/')
? 'public, max-age=31536000' // 1 year for static
: 'public, max-age=300', // 5 minutes for dynamic
// Performance hints
'X-Response-Time': Date.now() - request.startTime,
'X-Content-Length': JSON.stringify(responseBody).length
}
};
return optimizedResponse;
};
Task Function Patterns
Error Handling and Retries
export const handler = async (event) => {
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
try {
const result = await performTask();
return {
statusCode: 200,
body: JSON.stringify({
success: true,
attempt: attempt + 1,
result
})
};
} catch (error) {
attempt++;
console.error(`Task attempt ${attempt} failed:`, error);
if (attempt >= maxRetries) {
// Send notification on final failure
await sendTaskFailureNotification(error);
return {
statusCode: 500,
body: JSON.stringify({
error: 'Task failed after maximum retries',
attempts: attempt,
lastError: error.message
})
};
}
// Wait before retrying (exponential backoff)
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
};
export const handler = async (event) => {
try {
const items = await getItemsToProcess();
const batchSize = 50;
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
console.log(`Processing batch ${Math.floor(i/batchSize) + 1}/${Math.ceil(items.length/batchSize)}`);
const batchResults = await Promise.allSettled(
batch.map(item => processItem(item))
);
results.push(...batchResults);
// Brief pause between batches
if (i + batchSize < items.length) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
const successful = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
return {
statusCode: 200,
body: JSON.stringify({
message: 'Batch processing completed',
total: items.length,
successful,
failed,
batches: Math.ceil(items.length / batchSize)
})
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}
};
export const handler = async (event) => {
const taskId = `task-${Date.now()}`;
try {
// Initialize progress tracking
await updateTaskProgress(taskId, { status: 'started', progress: 0 });
const items = await getItemsToProcess();
const totalItems = items.length;
for (let i = 0; i < items.length; i++) {
await processItem(items[i]);
// Update progress every 10 items or at the end
if ((i + 1) % 10 === 0 || i === items.length - 1) {
const progress = Math.round(((i + 1) / totalItems) * 100);
await updateTaskProgress(taskId, {
status: 'running',
progress,
processed: i + 1,
total: totalItems
});
}
}
// Mark as completed
await updateTaskProgress(taskId, {
status: 'completed',
progress: 100,
processed: totalItems,
total: totalItems,
completedAt: new Date().toISOString()
});
return {
statusCode: 200,
body: JSON.stringify({
taskId,
message: 'Task completed successfully',
processed: totalItems
})
};
} catch (error) {
await updateTaskProgress(taskId, {
status: 'failed',
error: error.message,
failedAt: new Date().toISOString()
});
throw error;
}
};
Schedule Formats
// Every day at 2:30 AM
cron(30 2 * * ? *)
// Every weekday at 9:00 AM
cron(0 9 ? * MON-FRI *)
// Every 15 minutes
cron(*/15 * * * ? *)
// First day of every month at midnight
cron(0 0 1 * ? *)
// Every Sunday at 6:00 PM
cron(0 18 ? * SUN *)
// Every 5 minutes
rate(5 minutes)
// Every hour
rate(1 hour)
// Every 2 days
rate(2 days)
// Every 30 seconds
rate(30 seconds)
{
"dailyReports": "cron(0 8 * * ? *)",
"weeklyCleanup": "cron(0 2 ? * SUN *)",
"monthlyBilling": "cron(0 0 1 * ? *)",
"frequentSync": "rate(5 minutes)",
"healthCheck": "rate(30 seconds)"
}
General Development Tips
Function Design
- Keep functions small and focused
- Use TypeScript for better developer experience
- Follow single responsibility principle
- Return consistent response formats
Error Handling
- Always use try-catch blocks
- Return appropriate HTTP status codes
- Log errors for debugging
- Provide helpful error messages
Performance
- Initialize resources outside the handler
- Use connection pooling for databases
- Implement caching where appropriate
- Batch operations when possible
Security
- Validate and sanitize all inputs
- Use environment variables for secrets
- Implement authentication and authorization
- Add rate limiting to prevent abuse
Response Function Guidelines
Fail Gracefully
Always return the original response if your enhancement fails. Don’t break the user experience.
Keep It Fast
Response functions should be lightweight. Heavy processing can slow down responses.
Be Selective
Not every response needs enhancement. Use conditional logic to enhance only when needed.
Log Errors
Log enhancement errors for debugging, but don’t let them affect the user’s response.
Task Function Guidelines
Keep Tasks Idempotent
Design tasks to handle being run multiple times safely. Check for existing work before processing.
Process in Batches
For large datasets, process items in small batches to avoid timeouts and memory issues.
Monitor and Alert
Set up monitoring for task failures and send notifications when critical tasks fail.
Handle Failures Gracefully
Implement retry logic with exponential backoff. Store failed items for manual review.
Next Steps
Local Development
Set up your development environment
Deployment
Deploy your functions to production
Environment Variables
Manage configuration and secrets
Pro Tip: Start with simple patterns and gradually add complexity. Focus on getting the core functionality working before optimizing for performance.