This PR adds support for exposing Swift OpenAPI Lambda functions behind
an Application Load Balancer (ALB), providing an alternative to API
Gateway for HTTP routing to Lambda functions.
## Changes
### New ALB Support
- **OpenAPILambdaALB Protocol**: New protocol for ALB integration
alongside existing API Gateway support
- **ALB Event Handling**: Added `ALBTargetGroupRequest` and
`ALBTargetGroupResponse` support
- **HTTP Request Conversion**: Extension methods to convert ALB events
to/from HTTP requests/responses
### Core Library Updates
- **ALB-related source files**: New `/Sources/ALB/` directory with
ALB-specific implementations
- **Event Type Support**: Support for `ALBTargetGroupRequest` events
from Elastic Load Balancing
- **Response Mapping**: Proper mapping from OpenAPI responses to ALB
target group responses
### Complete ALB Example
- **QuoteAPI ALB Example**: Full working example in
`Examples/quoteapi-alb/`
- **Infrastructure as Code**: Complete SAM template with VPC, subnets,
security groups, and ALB
- **Build System**: Makefile and Docker build support for ALB deployment
- **Documentation**: Comprehensive README with ALB-specific deployment
instructions
### Key Files Added
```
Sources/ALB/
├── OpenAPILambdaALB.swift
└── ALBTargetGroup+HTTPRequest.swift
Examples/quoteapi-alb/
├── Package.swift
├── template.yaml
├── Makefile
├── README.md
├── Sources/QuoteAPI/QuoteService.swift
├── Sources/QuoteAPI/openapi.yaml
├── Sources/QuoteAPI/openapi-generator-config.yaml
└── events/GetQuote.json
```
## Usage
### Simple ALB Integration
```swift
@main
struct QuoteServiceALBImpl: APIProtocol, OpenAPILambdaALB {
func register(transport: OpenAPILambdaTransport) throws {
try self.registerHandlers(on: transport)
}
static func main() async throws {
let service = QuoteServiceALBImpl()
try await service.run()
}
// Your OpenAPI implementation...
}
```
### Key Differences from API Gateway
- Uses `OpenAPILambdaALB` instead of `OpenAPILambdaHttpApi`
- Handles `ALBTargetGroupRequest` events instead of
`APIGatewayV2Request`
- Returns `ALBTargetGroupResponse` instead of `APIGatewayV2Response`
- Requires VPC infrastructure (included in SAM template)
- No built-in authorization (implement via custom middleware if needed)
## Benefits
- **Cost Optimization**: ALB can be more cost-effective for high-traffic
applications
- **VPC Integration**: Native VPC support for private network access
- **Load Balancing**: Advanced load balancing features and health checks
- **WebSocket Support**: Future WebSocket support through ALB
- **Flexibility**: Choice between API Gateway and ALB based on use case
## Testing
- ✅ ALB example builds successfully with `sam build`
- ✅ Local testing with `sam local invoke`
- ✅ Complete infrastructure deployment via SAM
- ✅ HTTP requests properly routed through ALB to Lambda
- ✅ OpenAPI specification compatibility maintained
## Deployment
Deploy the ALB example:
```bash
cd Examples/quoteapi-alb
sam build && sam deploy --guided
```
Test the deployed endpoint:
```bash
curl http://[alb-dns-name]/stocks/AAPL
```
## Backward Compatibility
This is a purely additive change:
- Existing API Gateway implementations continue to work unchanged
- No breaking changes to existing APIs
- New ALB support is opt-in via protocol conformance
AWS Lambda transport for Swift OpenAPI
This library provides an AWS Lambda transport for Swift OpenAPI generator
This library allows you to expose server-side Swift OpenAPI implementations as AWS Lambda functions with minimal code changes.
The library provides:
- A default AWS Lambda Swift function that consumes your OpenAPI service implementation
- Built-in support for Amazon API Gateway HTTP API events
- Re-exported dependencies to minimize
Package.swiftcomplexity
We strongly recommend never deploying an openly available API. The QuoteAPI example project shows you how to add a Lambda Authorizer function to the API Gateway.
Prerequisites
- AWS Account
- AWS CLI - configured with your AWS credentials
- AWS SAM CLI - for serverless deployment
- Docker Desktop - for cross compilation to Linux, when using macOS or Windows.
Quick Start
If you already have an OpenAPI definition, you already generated the server stubs, and wrote an implementation, here are the additional steps to expose your OpenAPI service implementation as a AWS Lambda function and an Amazon API Gateway HTTP API (aka APIGatewayV2).
To expose your OpenAPI implementation as an AWS Lambda function:
1. Add dependencies to Package.swift
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator.git", from: "1.4.0"),
.package(url: "https://github.com/apple/swift-openapi-runtime.git", from: "1.8.2"),
// add these three dependencies
.package(url: "https://github.com/awslabs/swift-aws-lambda-runtime.git", from: "2.0.0"),
.package(url: "https://github.com/awslabs/swift-aws-lambda-events.git", from: "1.2.0"),
.package(url: "https://github.com/awslabs/swift-openapi-lambda.git", from: "2.0.0"),
],
targets: [
.executableTarget(
name: "YourOpenAPIService",
dependencies: [
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
// add these three products
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
.product(name: "OpenAPILambda", package: "swift-openapi-lambda"),
]
)
]
2. Update your service implementation
Add a protocol and a constructor to your existing OpenAPI service implementation
There are only five changes to make to your existing implementation:
import OpenAPIRuntime
import OpenAPILambda // <-- 1. import this library
@main // <-- 2. flag this struct as the executable target entrypoint
struct QuoteServiceImpl: APIProtocol, OpenAPILambdaHttpApi { // <-- 3. add the OpenAPILambdaHttpApi protocol
// The registration of your OpenAPI handlers
func register(transport: OpenAPILambdaTransport) throws { // <-- 4. add this method (calls registerHandlers)
try self.registerHandlers(on: transport)
}
// the entry point for your Lambda function
static func main() async throws { // <-- 5. add this entry point to start the lambdaRuntime
let openAPIService = QuoteServiceImpl()
try await openAPIService.run()
}
// Your existing OpenAPI implementation
func getQuote(_ input: Operations.getQuote.Input) async throws -> Operations.getQuote.Output {
let symbol = input.path.symbol
let price = Components.Schemas.quote(
symbol: symbol,
price: Double.random(in: 100..<150).rounded(),
change: Double.random(in: -5..<5).rounded(),
changePercent: Double.random(in: -0.05..<0.05),
volume: Double.random(in: 10000..<100000).rounded(),
timestamp: Date()
)
return .ok(.init(body: .json(price)))
}
}
3. Deploy with SAM
sam build && sam deploy --guided
Complete Example
See the Examples/quoteapi directory for a complete working example that includes:
- Stock quote API with OpenAPI 3.1 specification
- Lambda authorizer for protected endpoints
- Use
makefor common commands - SAM deployment configuration
- Local testing setup
Testing
Local Development
# Run locally with built-in development server
swift run QuoteService
# Test from another terminal
curl -H 'Authorization: Bearer 123' -X POST \
--data @events/GetQuote.json \
http://127.0.0.1:7000/invoke
Production Testing
# Test deployed API (replace with your endpoint)
curl -H 'Authorization: Bearer 123' \
https://your-api-id.execute-api.region.amazonaws.com/stocks/AAPL
Deployment Costs
New AWS accounts get 1 million Lambda invocations and 1 million API Gateway requests free per month. After the free tier, costs are approximately $1.00 per million API calls.
Cleanup
sam delete
Lambda Authorizers
The library supports Lambda authorizers for API protection. See Examples/quoteapi/Sources/LambdaAuthorizer for a complete implementation that validates a Bearer token.
let simpleAuthorizerHandler: (APIGatewayLambdaAuthorizerRequest, LambdaContext) async throws -> APIGatewayLambdaAuthorizerSimpleResponse = {
guard let authToken = $0.headers["authorization"],
authToken == "Bearer 123" else {
return .init(isAuthorized: false, context: [:])
}
return .init(isAuthorized: true, context: ["user": "authenticated"])
}
Advanced Usage
Custom Event Types
To support other Lambda event types beyond API Gateway, implement the OpenAPILambda protocol:
@main
struct CustomServiceLambda: OpenAPILambda {
typealias Event = YourCustomEvent
typealias Output = YourCustomResponse
func register(transport: OpenAPILambdaTransport) throws {
let handler = YourServiceImpl()
try handler.registerHandlers(on: transport)
}
func request(context: LambdaContext, from event: Event) throws -> OpenAPILambdaRequest {
// Transform your event to HTTPRequest
}
func output(from response: OpenAPILambdaResponse) -> Output {
// Transform HTTPResponse to your output type
}
}
Service Lifecycle Integration
import ServiceLifecycle
// In your OpenAPI service, explicitly create and manage the LambdaRuntime
static func main() async throws {
let lambdaRuntime = try LambdaRuntime(body: Self.handler())
let serviceGroup = ServiceGroup(
services: [lambdaRuntime],
gracefulShutdownSignals: [.sigterm],
cancellationSignals: [.sigint],
logger: Logger(label: "ServiceGroup")
)
try await serviceGroup.run()
}
Dependency Injection
For advanced use cases requiring dependency injection:
@main
struct QuoteServiceImpl: APIProtocol, OpenAPILambdaHttpApi {
let customDependency: Int
init(customDependency: Int = 0) {
self.customDependency = customDependency
}
// the entry point can be in another file / struct as well.
static func main() async throws {
let service = QuoteServiceImpl(customDependency: 42)
let lambda = try OpenAPILambdaHandler(service: service)
let lambdaRuntime = LambdaRuntime(body: lambda.handler)
try await lambdaRuntime.run()
}
func register(transport: OpenAPILambdaTransport) throws {
try self.registerHandlers(on: transport)
}
}
References
- Swift OpenAPI Generator - Complete documentation and tutorials
- Swift AWS Lambda Runtime - Swift runtime for AWS Lambda
- AWS SAM - Serverless Application Model documentation
- API Gateway Lambda Authorizers - Lambda authorization documentation
