This post is for whom want to use AWS Lambda with OpenAPI Generator. Official guide is useful but I felt there are some missing information. So I wrote this post. You can successfully run AWS Lambda function on your local machine and debug your code.

Let’s start from very simple example. You only need 4 files. I’ll explain details. (Ignore Tests folder, no need it in this tutorial)

  • Package.swift
  • NativeMobileServer.swift
  • openapi.yaml
  • openapi-generator-config.yaml

Step 1. Define OpenAPI Spec

This OpenAPI spec is for tutorial.

Please check folder and file structure. Create an openapi.yaml

openapi: 3.1.0
info:
  title: MobileJobService
  version: 1.0.0

paths:
  /jobs/fetch:
    post:
      summary: Fetch job data from external source
      description: >
        This endpoint is called by a scheduled Lambda or backend service.
        It fetches job data from the given URL and processes it using the provided prompt.
      operationId: fetchJobs
      tags:
        - jobs
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FetchJobsRequest'
      responses:
        '200':
          description: Successfully fetched and processed job data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobListResponse'
        '400':
          description: Invalid input parameters
        '500':
          description: Internal error during job fetch or processing

components:
  schemas:
    FetchJobsRequest:
      type: object
      required:
        - url
        - prompt
      properties:
        url:
          type: string
          format: uri
          description: Target URL to scrape or fetch job data from
        prompt:
          type: string
          description: Instruction / extraction prompt used to parse the fetched page

    JobListResponse:
      type: array
      items:
        $ref: '#/components/schemas/Job'

    Job:
      type: object
      required:
        - id
        - title
      properties:
        id:
          type: string
          description: Unique identifier for the job
        title:
          type: string
          description: Job title
        country:
          type: string
          description: Country code (e.g. SG, US, TW)
        city:
          type: string
          description: City name (e.g. Singapore)
        postedAt:
          type: string
          format: date-time
          description: When this job was posted, if known
        company:
          type: string
          description: Company name
        team:
          type: string
          description: Team / department (e.g. Mobile, Backend, Growth)
        jobDescriptionLink:
          type: string
          format: uri
          description: Public link to full job description
        jobApplyLink:
          type: string
          format: uri
          description: Public link to apply
        salary:
          $ref: '#/components/schemas/Salary'
        description:
          type: string
          description: Cleaned / extracted full-text description for the role

    Salary:
      type: object
      properties:
        min:
          type: number
          description: Minimum compensation
        max:
          type: number
          description: Maximum compensation
        basis:
          type: string
          enum: [year, month]
          description: Salary period basis (yearly or monthly)

And then create openapi-generator-config.yaml

generate:
  - types
  - server

Step 2. Create Swift Package Manger

Package.swift

// swift-tools-version: 6.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "NativeMobileServer",
    platforms: [
        .macOS(.v15)
    ],
    products: [
        .executable(name: "NativeMobileServer", targets: ["NativeMobileServer"])
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-openapi-generator.git", from: "1.10.3"),
        .package(url: "https://github.com/apple/swift-openapi-runtime.git", from: "1.8.2"),

        .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"),

        .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.0.0"),
    ],
    targets: [
        .executableTarget(
            name: "NativeMobileServer",
            dependencies: [
                .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),

                .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
                .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),

                .product(name: "OpenAPILambda", package: "swift-openapi-lambda"),
                .product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
            ],
            path: "Sources/NativeMobileServer",
            resources: [
                .copy("openapi.yaml"),
                .copy("openapi-generator-config.yaml")
            ],
            plugins: [
                .plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")
            ]
        ),
        .testTarget(
            name: "NativeMobileServerTests",
            dependencies: ["NativeMobileServer"]
        ),
    ]
)

You can remove .testTarget if you don’t need to write test cases.


Step 3. Write a main function

//
//  NativeMobileServer.swift
//  NativeMobileServer
//
//  Created by Shawn Sungwook Baek on 10/26/25.
//

import Foundation
import Logging
import OpenAPILambda
import OpenAPIRuntime

@main
struct JobServiceImpl: APIProtocol, OpenAPILambdaHttpApi {
    func fetchJobs(_ input: Operations.fetchJobs.Input) async throws -> Operations.fetchJobs.Output
    {
        logger.info("fetchJobs invoked")
        let requestBody = input
       
        if case let .json(request) = requestBody.body {
            logger.info("Fetching jobs from \(request.url) with prompt: \(request.prompt)")
        }

        let mockJobs: [Components.Schemas.Job] = [
            .init(
                id: "job-001",
                title: "iOS Developer",
                country: "SG",
                postedAt: Date(),
                company: "Apple",
                team: "Mobile",
                jobDescriptionLink: "https://example.com/job/1",
                jobApplyLink: "https://example.com/apply/1",
                salary: .init(min: 6000, max: 9000, basis: .month),
                description: "Develop and maintain the iOS application."
            ),
            .init(
                id: "job-002",
                title: "Backend Engineer",
                country: "TW",
                postedAt: Date(),
                company: "Uber",
                team: "Server",
                jobDescriptionLink: "https://example.com/job/2",
                jobApplyLink: "https://example.com/apply/2",
                salary: .init(min: 7000, max: 10000, basis: .month),
                description: "Build API services and integrations."
            ),
        ]
        return .ok(.init(body: .json(mockJobs)))
    }

    let logger: Logger

    func register(transport: OpenAPILambdaTransport) throws {
        try transport.router.get("/health") { _, _ in
            "OK"
        }
        logger.trace("Available Routes\n\(transport.router)")
        // to log all requests and their responses, add a logging middleware
        let loggingMiddleware = LoggingMiddleware(logger: logger)

        // MANDATORY (middlewares are optional)
        try self.registerHandlers(on: transport, middlewares: [loggingMiddleware])
    }

    static func main() async throws {
        let openAPIService = JobServiceImpl(loggerLabel: "JobService")
        try await openAPIService.run()

    }

    init(loggerLabel: String) {
        var logger = Logger(label: loggerLabel)
        logger.logLevel = .trace
        self.logger = logger
    }
}

Step 4. Check Scheme in Xcode

Check Executable Target.

Important!

You need to set Environment Variables

  • LOCAL_LAMBDA_SERVER_ENABLED
  • LOCAL_LAMBDA_PORT <- Optional! Default port is 7000

Try build and run and check which pid is using 7000 port.

lsof -i :7000

If other program uses that port, you can change default port by setting LOCAL_LAMBDA_PORT

When you run it, you will see the message. (In my case, I changed the default port 8000)

Step 5. Invoke Lambda function

This part you may confusing because you can’t call lambda like

curl -v -X POST http://127.0.0.1:8000/jobs/fetch \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/jobs",
    "prompt": "Extract latest mobile job listings"
  }'

If you want to see more detail information, check this out

To invoke and debug our code, we should invoke function like this.

If you want to learn more about this payload structure v2.0 in API Gateway check here

curl -X "POST" "http://127.0.0.1:8000/invoke" \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "requestContext": {
    "accountId": "",
    "time": "",
    "http": {
      "path": "/jobs/fetch",
      "userAgent": "",
      "method": "POST",
      "protocol": "HTTP/1.1",
      "sourceIp": "127.0.0.1"
    },
    "domainName": "",
    "timeEpoch": 0,
    "domainPrefix": "",
    "apiId": "",
    "requestId": "",
    "stage": "$default"
  },
  "rawPath": "/jobs/fetch",
  "rawQueryString": "",
  "version": "2.0",
  "routeKey": "$default",
  "isBase64Encoded": true,
  "body": "{\\"url\\":\\"https://jobs.apple.com/en-sg/search\\", \\"prompt\\":\\"Extract latest mobile job listings\\"}"
}'


I’ve set breakpoint at here.

Let’s invoke function.

Ok, breakpoint is working.

And I can see the results from AWS Lambda function

Conclusion

Swift is very powerful for developing server-side applications. There a lot of great open source projects. In Part 2, I’ll explain how to deploy AWS Lambda Swift function to the AWS using SAM CLI.

Leave a comment

Quote of the week

"People ask me what I do in the winter when there's no baseball. I'll tell you what I do. I stare out the window and wait for spring."

~ Rogers Hornsby