swift-openapi-lambda

2.1.0

An AWS Lambda transport for Swift OpenAPI
awslabs/swift-openapi-lambda

What's New

2.1.0

2025-10-26T08:24:30Z

This release is the first one in the repo new home (/awslabs).

We also introduce support for a new event type: Application Load Balancer (ALB). You can now expose your OpenAPI Lambda function behind an ALB. In your code, just conform to the OpenAPILambdaALB (instead of the OpenAPILambdaHttpAPI that you use for the API Gateway v2).

Look at the new example in Examples/quoteapi-alb directory.

@main
struct QuoteServiceALBImpl: APIProtocol, OpenAPILambdaALB {

    func register(transport: OpenAPILambdaTransport) throws {
        try self.registerHandlers(on: transport)
    }

    static func main() async throws {
        let openAPIService = QuoteServiceALBImpl()
        try await openAPIService.run()
    }

    // the functions generated by the OpenAPI plugin
    func getQuote(_ input: Operations.getQuote.Input) async throws -> Operations.getQuote.Output {
    ... 
    }
}

We also changed some of the legal docs (CONTRIBUTING, NOTICE, and the license headers) to match AWS's defaults.

As usual, continue to share your feedback and open issues. The full change log is below.

ALB or API Gateway?

  • ALB integrates serverless Lambdas with traditional backend targets, supporting flexible routing and basic authentication.​
  • API Gateway v2 is a managed gateway for REST, HTTP, and WebSocket APIs, offering rich features such as request validation, usage plans, custom authorizers, and integration with AWS managed services (e.g., Cognito).

My mental model to choose which one to use

  • ALB fits when integrating Lambdas into broader web applications, distributing HTTP traffic or exposing serverless functions alongside container services or EC2.​
  • API Gateway is best for building public APIs, complex microservices, or needs requiring request validation, throttling, and security integrations (OAuth, API Key, CSP).​
  • Cost: ALB is generally cheaper at scale and for high RPS traffic, while API Gateway may be more economical for low-volume APIs due to its rich feature set.​
  • API Gateway allows multiple API versions and SDK auto-generation, aiding mobile and client app backward compatibility.​
  • Latency: API Gateway introduces more overhead compared to ALB, but offers more features like transformation, validation, and direct AWS integrations.

What's Changed

SemVer Minor

  • Add support for Lambda functions exposed behind an Application Load Balancer (ALB) by @sebsto in #29

SemVer Patch

  • Change links to /swift-server to /awslabs by @sebsto in #26
  • Remove GH Action on push by @sebsto in #27
  • Adjust license files for /awslabs by @sebsto in #28

Other Changes

Full Changelog: 2.0.0...2.1.0

Build & Test on GitHub

language language platform platform license

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.swift complexity

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

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:

Animated GIF to show the changes

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 make for 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

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

Last updated: Mon Oct 27 2025 07:56:48 GMT-0900 (Hawaii-Aleutian Daylight Time)