AWS Lambda Deployment¶
Deploy RestMachine applications to AWS Lambda for serverless, scalable REST APIs. RestMachine provides seamless integration with API Gateway, Application Load Balancer (ALB), and Lambda Function URLs.
Installation¶
Install RestMachine with AWS support:
Or install separately:
Quick Start¶
Create a Lambda handler:
# lambda_function.py
from restmachine import RestApplication
from restmachine_aws import AwsApiGatewayAdapter
# Create application
app = RestApplication()
@app.get("/")
def home():
return {"message": "Hello from Lambda!"}
@app.get("/users/{user_id}")
def get_user(path_params):
return {"id": path_params['user_id']}
# Create Lambda handler
adapter = AwsApiGatewayAdapter(app)
def lambda_handler(event, context):
"""AWS Lambda handler function."""
return adapter.handle_event(event, context)
API Gateway Integration¶
RestMachine automatically detects and handles different API Gateway event formats.
HTTP API (v2) - Recommended¶
Modern, cost-effective API Gateway:
# SAM template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
RestMachineApi:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: lambda_function.lambda_handler
Runtime: python3.11
Events:
ApiEvent:
Type: HttpApi
Properties:
Path: /{proxy+}
Method: ANY
REST API (v1)¶
Traditional API Gateway:
Lambda Function URLs¶
Direct HTTPS endpoint:
Application Load Balancer¶
ALB events are detected automatically - no special configuration needed.
Deployment Methods¶
AWS SAM (Recommended)¶
template.yaml:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
MyApi:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: lambda_function.lambda_handler
Runtime: python3.11
Timeout: 30
MemorySize: 512
Environment:
Variables:
LOG_LEVEL: INFO
Events:
HttpApi:
Type: HttpApi
Properties:
Path: /{proxy+}
Method: ANY
requirements.txt:
Deploy:
Serverless Framework¶
serverless.yml:
service: restmachine-api
provider:
name: aws
runtime: python3.11
stage: ${opt:stage, 'dev'}
region: us-east-1
functions:
api:
handler: lambda_function.lambda_handler
events:
- httpApi:
path: /{proxy+}
method: ANY
Deploy:
Lifecycle Management¶
Startup Handlers¶
Startup handlers run once per Lambda cold start:
app = RestApplication()
@app.on_startup
def database():
"""Initialize database connection (runs once per cold start)."""
import boto3
dynamodb = boto3.resource('dynamodb')
return dynamodb.Table('users')
@app.get("/users/{user_id}")
def get_user(path_params, database):
"""Use database connection from startup handler."""
response = database.get_item(Key={'id': path_params['user_id']})
return response.get('Item', {})
Cold starts: Startup handlers run once when Lambda initializes a new container.
Warm invocations: Subsequent requests reuse the startup handler results.
Shutdown Handlers¶
For resource cleanup when Lambda containers terminate, RestMachine supports Lambda Extensions. See the AWS package documentation for advanced Lambda Extension support.
Database Integration¶
DynamoDB¶
import boto3
@app.on_startup
def dynamodb():
"""Initialize DynamoDB client."""
table_name = os.getenv('TABLE_NAME', 'users')
return boto3.resource('dynamodb').Table(table_name)
@app.get("/items/{item_id}")
def get_item(path_params, dynamodb):
response = dynamodb.get_item(Key={'id': path_params['item_id']})
return response.get('Item', {})
@app.post("/items")
def create_item(json_body, dynamodb):
dynamodb.put_item(Item=json_body)
return json_body, 201
RDS with Secrets Manager¶
import boto3
import json
import psycopg2
@app.on_startup
def database():
"""Get RDS credentials and connect."""
secrets = boto3.client('secretsmanager')
secret = secrets.get_secret_value(SecretId='rds-credentials')
creds = json.loads(secret['SecretString'])
return psycopg2.connect(
host=creds['host'],
database=creds['database'],
user=creds['username'],
password=creds['password']
)
Configuration¶
Environment Variables¶
import os
TABLE_NAME = os.getenv('TABLE_NAME', 'default-table')
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
@app.on_startup
def dynamodb():
import boto3
return boto3.resource('dynamodb').Table(TABLE_NAME)
SAM template:
Local Testing¶
SAM Local¶
Test locally with SAM CLI:
Direct Invocation¶
# test_lambda.py
from lambda_function import lambda_handler
def test_get_user():
event = {
"version": "2.0",
"routeKey": "GET /users/{user_id}",
"rawPath": "/users/123",
"pathParameters": {"user_id": "123"},
"requestContext": {
"http": {"method": "GET", "path": "/users/123"}
}
}
response = lambda_handler(event, None)
assert response['statusCode'] == 200
Complete Example¶
# lambda_function.py
import os
import logging
import boto3
from restmachine import RestApplication
from restmachine_aws import AwsApiGatewayAdapter
from pydantic import BaseModel
# Configure logging
logger = logging.getLogger()
logger.setLevel(os.getenv('LOG_LEVEL', 'INFO'))
# Create application
app = RestApplication()
# Startup handler
@app.on_startup
def dynamodb():
"""Initialize DynamoDB (runs once per cold start)."""
table_name = os.getenv('TABLE_NAME', 'users')
logger.info(f"Connecting to table: {table_name}")
return boto3.resource('dynamodb').Table(table_name)
# Validation
class UserCreate(BaseModel):
id: str
name: str
@app.validates
def user_create(json_body) -> UserCreate:
return UserCreate.model_validate(json_body)
# Routes
@app.get("/health")
def health():
return {"status": "healthy"}
@app.get("/users/{user_id}")
def get_user(path_params, dynamodb):
try:
response = dynamodb.get_item(Key={'id': path_params['user_id']})
item = response.get('Item')
if not item:
return {"error": "User not found"}, 404
return item
except Exception as e:
logger.error(f"Error: {e}")
return {"error": "Internal server error"}, 500
@app.post("/users")
def create_user(user_create: UserCreate, dynamodb):
try:
dynamodb.put_item(Item=user_create.model_dump())
return user_create.model_dump(), 201
except Exception as e:
logger.error(f"Error: {e}")
return {"error": "Internal server error"}, 500
# Create adapter and handler
adapter = AwsApiGatewayAdapter(app)
def lambda_handler(event, context):
"""AWS Lambda handler function."""
return adapter.handle_event(event, context)
Complete SAM template:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]
Resources:
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub users-${Environment}
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: lambda_function.lambda_handler
Runtime: python3.11
Timeout: 30
MemorySize: 512
Environment:
Variables:
TABLE_NAME: !Ref UsersTable
LOG_LEVEL: INFO
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Events:
HttpApi:
Type: HttpApi
Properties:
Path: /{proxy+}
Method: ANY
Outputs:
ApiUrl:
Description: API Gateway endpoint URL
Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com'
Performance Optimization¶
Lambda Configuration¶
Configure memory (more memory = more CPU):
Cold Start Optimization¶
- Use startup handlers to initialize connections once
- Package only needed dependencies - smaller packages load faster
- Use Lambda Layers for dependencies
- Keep functions warm with provisioned concurrency (for critical paths)
Reuse Connections¶
# Good - connection reused across invocations
@app.on_startup
def database():
return boto3.resource('dynamodb')
# Bad - creates new connection each request
@app.get("/users")
def get_users():
db = boto3.resource('dynamodb') # Don't do this!
Best Practices¶
1. Use Environment Variables¶
2. Implement Health Checks¶
3. Log Structured Data¶
4. Handle Errors Gracefully¶
try:
result = database.query(...)
return result
except Exception as e:
logger.error(f"Error: {e}", exc_info=True)
return {"error": "Internal server error"}, 500
5. Use IAM for Permissions¶
Grant least-privilege permissions:
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref MyTable
- Statement:
- Effect: Allow
Action: secretsmanager:GetSecretValue
Resource: !Ref MySecret
Monitoring¶
CloudWatch Logs¶
Logs automatically sent to CloudWatch:
CloudWatch Metrics¶
Track: - Invocations - Duration - Errors - Throttles - Cold starts
X-Ray Tracing¶
Enable in SAM:
Next Steps¶
- ASGI Deployment → - Deploy with ASGI servers
- Lifecycle Management → - Advanced startup/shutdown patterns
- Performance → - Optimization techniques