AWS Lambda
Deploy your Vercube application to AWS Lambda with full support for API Gateway v1 and v2, automatic binary content handling, and seamless cookie management.
Basic Setup
import { createApp } from '@vercube/core';
import { toServerlessHandler } from '@vercube/serverless/aws-lambda';
const app = createApp();
// Your controllers are automatically registered
export const handler = toServerlessHandler(app);
Supported API Gateway Versions
The adapter automatically detects and supports both API Gateway versions:
API Gateway v1 (REST API)
Works with APIGatewayProxyEvent:
// API Gateway v1 Event Format
{
httpMethod: 'GET',
path: '/api/users',
headers: {
'content-type': 'application/json',
'authorization': 'Bearer token...'
},
body: '{"name":"John"}',
queryStringParameters: {
page: '1',
limit: '10'
},
pathParameters: {
id: '123'
}
}
Key Features:
- Traditional REST API structure
- Simple header handling
- Cookie handling via
Set-Cookieheader - Binary content with
isBase64Encodedflag
API Gateway v2 (HTTP API)
Works with APIGatewayProxyEventV2:
// API Gateway v2 Event Format
{
requestContext: {
http: {
method: 'GET',
path: '/api/users'
}
},
headers: {
'content-type': 'application/json',
'authorization': 'Bearer token...'
},
body: '{"name":"John"}',
queryStringParameters: {
page: '1',
limit: '10'
},
pathParameters: {
id: '123'
}
}
Key Features:
- Improved performance and lower cost
- Enhanced cookie handling with cookies array
- Streamlined event structure
- Better WebSocket support
Binary Content Handling
Binary responses are automatically encoded as base64:
@Controller('/files')
export class FileController {
@Inject(FileService)
private fileService!: FileService;
@Get('/download/:id')
async downloadFile(@Param('id') id: string) {
const fileBuffer = await this.fileService.getFile(id);
return new Response(fileBuffer, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="file-${id}.pdf"`
}
});
}
@Get('/image/:id')
async getImage(@Param('id') id: string) {
const imageBuffer = await this.fileService.getImage(id);
return new Response(imageBuffer, {
headers: {
'Content-Type': 'image/png'
}
});
}
}
The adapter automatically:
- Detects binary content types
- Encodes the body as base64
- Sets
isBase64Encoded: truein the response - API Gateway decodes it for the client
Configure Binary Media Types
Tell API Gateway which content types should be treated as binary:
functions:
api:
handler: lambda.handler
events:
- http:
path: /{proxy+}
method: ANY
binaryMediaTypes:
- 'image/*'
- 'application/pdf'
- 'application/zip'
- 'application/octet-stream'
Cookie Handling
Cookies work seamlessly across both API Gateway versions:
@Controller('/auth')
export class AuthController {
@Inject(AuthService)
private authService!: AuthService;
@Post('/login')
async login(@Body({ validationSchema: LoginSchema }) credentials: LoginDto) {
const token = await this.authService.generateToken(credentials);
// Set authentication cookie
return FastResponse.ok({ success: true })
.cookie('auth_token', token, {
httpOnly: true,
secure: true,
maxAge: 3600,
sameSite: 'strict'
});
}
@Post('/logout')
async logout() {
// Clear authentication cookie
return FastResponse.ok({ success: true })
.cookie('auth_token', '', {
httpOnly: true,
secure: true,
maxAge: 0
});
}
@Get('/session')
async getSession(@Cookie('auth_token') token: string) {
if (!token) {
throw new UnauthorizedException('Not authenticated');
}
const session = await this.authService.validateToken(token);
return { session };
}
}
How it works:
- API Gateway v1: Cookies set via
Set-Cookieheader - API Gateway v2: Cookies set via
cookiesarray for better handling
Environment Variables
Access Lambda-specific environment variables:
import { RuntimeConfig } from '@vercube/core';
@Controller('/config')
export class ConfigController {
@Get('/lambda-info')
getLambdaInfo() {
return {
// AWS Lambda environment variables
region: process.env.AWS_REGION,
functionName: process.env.AWS_LAMBDA_FUNCTION_NAME,
functionVersion: process.env.AWS_LAMBDA_FUNCTION_VERSION,
memoryLimit: process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE,
logGroup: process.env.AWS_LAMBDA_LOG_GROUP_NAME,
logStream: process.env.AWS_LAMBDA_LOG_STREAM_NAME
};
}
@Get('/runtime-info')
getRuntimeInfo() {
return {
// Runtime execution details
requestId: process.env.AWS_REQUEST_ID,
executionEnv: process.env.AWS_EXECUTION_ENV,
runtime: process.env.AWS_LAMBDA_RUNTIME_API
};
}
}
Serverless Framework Configuration
Basic Configuration
service: vercube-api
provider:
name: aws
runtime: nodejs22.x
region: us-east-1
memorySize: 512
timeout: 30
# Environment variables
environment:
NODE_ENV: production
DATABASE_URL: ${env:DATABASE_URL}
JWT_SECRET: ${env:JWT_SECRET}
functions:
api:
handler: lambda.handler
events:
- http:
path: /{proxy+}
method: ANY
cors: true
Advanced Configuration
service: vercube-api
provider:
name: aws
runtime: nodejs22.x
region: us-east-1
# Performance settings
memorySize: 1024
timeout: 60
# VPC configuration (for database access)
vpc:
securityGroupIds:
- sg-xxxxxxxxx
subnetIds:
- subnet-xxxxxxxxx
- subnet-yyyyyyyyy
# IAM permissions
iam:
role:
statements:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource: 'arn:aws:s3:::my-bucket/*'
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
Resource: 'arn:aws:dynamodb:${aws:region}:*:table/my-table'
# Environment variables
environment:
NODE_ENV: ${opt:stage, 'dev'}
DATABASE_URL: ${env:DATABASE_URL}
REDIS_URL: ${env:REDIS_URL}
JWT_SECRET: ${env:JWT_SECRET}
S3_BUCKET: ${env:S3_BUCKET}
functions:
api:
handler: lambda.handler
# Provisioned concurrency for predictable performance
provisionedConcurrency: 2
# Reserved concurrent executions
reservedConcurrency: 100
events:
- http:
path: /{proxy+}
method: ANY
cors:
origin: '*'
headers:
- Content-Type
- Authorization
- X-Api-Key
allowCredentials: true
# Binary media types
binaryMediaTypes:
- 'image/*'
- 'application/pdf'
- 'application/zip'
# Layer for dependencies
layers:
- arn:aws:lambda:us-east-1:xxxxx:layer:my-dependencies:1
# Tags
tags:
Environment: ${opt:stage, 'dev'}
Service: vercube-api
# Plugins
plugins:
- serverless-offline
- serverless-plugin-typescript
# Custom configuration
custom:
serverless-offline:
httpPort: 3000
Cold Start Optimization
Minimize cold start times for better performance:
Module-Level Initialization
import { createApp } from '@vercube/core';
import { toServerlessHandler } from '@vercube/serverless/aws-lambda';
import { DatabaseService } from './services/Database';
// Initialize app at module level (happens once per container)
const app = createApp({
setup: async (app) => {
// Bind services
app.container.bind(DatabaseService);
// Initialize database connection (reused across invocations)
const db = app.container.get(DatabaseService);
await db.connect();
}
});
// Export handler (execution is fast)
export const handler = toServerlessHandler(app);
Lazy Loading Heavy Dependencies
export class HeavyServiceFactory {
private static instance: HeavyService | null = null;
static async getInstance() {
if (!this.instance) {
// Only import when needed
const { HeavyService } = await import('./HeavyService');
this.instance = new HeavyService();
}
return this.instance;
}
}
@Controller('/heavy')
export class HeavyController {
@Get('/process')
async process() {
// Load heavy service only when this endpoint is called
const service = await HeavyServiceFactory.getInstance();
return await service.process();
}
}
Provisioned Concurrency
Keep functions warm to eliminate cold starts:
functions:
api:
handler: lambda.handler
provisionedConcurrency: 5 # Keep 5 instances always warm
# Or use auto-scaling
provisionedConcurrency:
minCapacity: 2
maxCapacity: 10
targetUtilizationPercent: 0.75
Database Connections
Handle database connections efficiently in Lambda:
Connection Pooling
export class DatabaseService {
private static pool: Pool | null = null;
async getPool() {
// Reuse connection pool across invocations
if (DatabaseService.pool) {
return DatabaseService.pool;
}
DatabaseService.pool = new Pool({
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
// Lambda-optimized settings
max: 1, // Single connection per Lambda instance
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
});
return DatabaseService.pool;
}
async query(sql: string, params?: any[]) {
const pool = await this.getPool();
return await pool.query(sql, params);
}
}
RDS Proxy
Use RDS Proxy to manage database connections:
provider:
environment:
DB_HOST: my-rds-proxy.proxy-xxxxxxxxx.us-east-1.rds.amazonaws.com
DB_PORT: 5432
iam:
role:
statements:
- Effect: Allow
Action:
- rds-db:connect
Resource: 'arn:aws:rds-db:us-east-1:xxxxx:dbuser:prx-xxxxx/*'
Advanced Response Handling
Custom Headers
@Controller('/api')
export class ApiController {
@Get('/cached-data')
getCachedData() {
return FastResponse.ok({ data: 'cached' })
.header('Cache-Control', 'public, max-age=3600')
.header('X-Custom-Header', 'custom-value');
}
@Get('/streaming-data')
getStreamingData() {
const stream = this.createDataStream();
return new Response(stream, {
headers: {
'Content-Type': 'application/json',
'Transfer-Encoding': 'chunked'
}
});
}
}
Error Handling
import { NotFoundException, BadRequestException, InternalServerErrorException } from '@vercube/core';
@Controller('/users')
export class UserController {
@Get('/:id')
async getUser(@Param('id') id: string) {
try {
const user = await this.userService.findById(id);
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
} catch (error) {
if (error instanceof NotFoundException) {
throw error;
}
// Log unexpected errors
console.error('Error fetching user:', error);
throw new InternalServerErrorException('Failed to fetch user');
}
}
}
// Automatically returns proper AWS response:
// {
// statusCode: 404,
// headers: { 'Content-Type': 'application/json' },
// body: '{"statusCode":404,"message":"User with ID ... not found"}'
// }
Deployment
Deploy to AWS
# Deploy to default stage (dev)
serverless deploy
# Deploy to production
serverless deploy --stage prod
# Deploy specific function
serverless deploy function -f api
# Deploy with verbose output
serverless deploy --verbose
Environment-Specific Deployments
service: vercube-api
provider:
name: aws
runtime: nodejs22.x
region: ${opt:region, 'us-east-1'}
stage: ${opt:stage, 'dev'}
environment:
NODE_ENV: ${self:provider.stage}
DATABASE_URL: ${env:DATABASE_URL_${self:provider.stage}}
functions:
api:
handler: lambda.handler
# Deploy to development
serverless deploy --stage dev
# Deploy to production
serverless deploy --stage prod --region us-west-2
Monitoring and Debugging
CloudWatch Metrics
Monitor your Lambda function performance:
functions:
api:
handler: lambda.handler
# Enable detailed CloudWatch metrics
tracing:
lambda: true
apiGateway: true
Key Metrics to Monitor:
- Invocations - Number of times function is invoked
- Duration - Execution time per invocation
- Errors - Number of failed invocations
- Throttles - Number of throttled invocations
- ConcurrentExecutions - Number of concurrent invocations
- IteratorAge - For stream-based invocations
X-Ray Tracing
Enable AWS X-Ray for detailed tracing:
provider:
tracing:
lambda: true
apiGateway: true
functions:
api:
handler: lambda.handler
import AWSXRay from 'aws-xray-sdk-core';
import AWS from 'aws-sdk';
// Wrap AWS SDK
const XAWS = AWSXRay.captureAWS(AWS);
@Controller('/traced')
export class TracedController {
@Get('/data')
async getData() {
// This will appear in X-Ray traces
const segment = AWSXRay.getSegment();
const subsegment = segment.addNewSubsegment('custom-operation');
try {
const data = await this.processData();
subsegment.close();
return data;
} catch (error) {
subsegment.addError(error);
subsegment.close();
throw error;
}
}
}
Troubleshooting
Common Issues
Handler not found
Error: Cannot find module 'lambda'
Solution: Ensure handler path matches your file structure:
functions:
api:
handler: lambda.handler # <filename>.<export name>
Request timeout
Task timed out after 30.00 seconds
Solution: Increase timeout:
functions:
api:
timeout: 60
Binary content corrupted
Response body appears corrupted or truncated
Solution: Configure binary media types:
functions:
api:
events:
- http:
binaryMediaTypes:
- 'image/*'
- 'application/pdf'
Cold start too slow
Duration: 3000ms (Cold Start: 2500ms)
Solutions:
- Reduce deployment package size
- Use Lambda layers for dependencies
- Enable provisioned concurrency
- Lazy load heavy modules
Memory limit exceeded
Process exited before completing request
Solution: Increase memory:
functions:
api:
memorySize: 1024 # or higher