| name | add-server |
| description | Set up swift-openapi-generator for a server target (Vapor or Hummingbird). Use when adding a new server that serves an openapi.yaml. |
Set up swift-openapi-generator for a server target using Vapor or Hummingbird.
Ask for:
- Server framework: Vapor or Hummingbird
- Target name (e.g., ApiServer, AppServer)
- Path to the existing openapi.yaml (or create a new one)
- Whether to share types with a client target via a shared ApiShared package
Then:
Package.swift dependencies
Add the required dependencies based on framework choice:
Vapor:
swift-openapi-generator (build plugin)
swift-openapi-runtime
swift-openapi-vapor
vapor
Hummingbird:
swift-openapi-generator (build plugin)
swift-openapi-runtime
swift-openapi-hummingbird
hummingbird
Target setup
- Add the server target to Package.swift with the OpenAPI build plugin:
.target(
name: "{TargetName}",
dependencies: [
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
],
plugins: [
.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator"),
]
)
Configuration files
Create openapi-generator-config.yaml next to the openapi.yaml:
generate:
- types
- server
Implementation scaffold
Create the handler conforming to the generated APIProtocol:
Vapor:
import OpenAPIVapor
import Vapor
struct Handler: APIProtocol {
}
@main
struct App {
static func main() async throws {
let app = try await Vapor.Application.make()
let transport = VaporTransport(routesBuilder: app)
let handler = Handler()
try handler.registerHandlers(on: transport)
try await app.execute()
}
}
Hummingbird:
import OpenAPIHummingbird
import Hummingbird
struct Handler: APIProtocol {
}
@main
struct App {
static func main() async throws {
let router = Router()
let transport = HummingbirdTransport(router)
let handler = Handler()
try handler.registerHandlers(on: transport)
let app = Application(router: router)
try await app.run()
}
}
Shared types pattern (if sharing with client)
If types are shared between client and server, split generation across three targets:
ApiShared target generates only types from openapi.yaml
ApiClient target generates only client, depends on ApiShared
ApiServer target generates only server, depends on ApiShared
Each has its own openapi-generator-config.yaml, but they all reference the same openapi.yaml (via symlink or copy).
Tests
Always create a test target {TargetName}Tests with:
- A test client that hits the handler directly via in-memory transport (no real HTTP)
- At least one test per operation verifying the handler logic
- Use Swift Testing framework (
@Test, @Suite, #expect)
- Use
withDependencies for injecting test doubles (mock DB, mock services)
Vapor:
@Test func listProductsReturnsAllProducts() async throws {
let app = try await Application.make(.testing)
defer { app.shutdown() }
let handler = Handler()
let transport = VaporTransport(routesBuilder: app)
try handler.registerHandlers(on: transport)
try await app.test(.GET, "/products") { response in
#expect(response.status == .ok)
}
}
Hummingbird:
@Test func listProductsReturnsAllProducts() async throws {
let handler = Handler()
let router = Router()
let transport = HummingbirdTransport(router)
try handler.registerHandlers(on: transport)
let app = Application(router: router)
try await app.test(.router) { client in
let response = try await client.execute(uri: "/products", method: .get)
#expect(response.status == .ok)
}
}
Final steps
- Stub every operation from the spec with a
TODO and a meaningful error response
- Run
swift build to confirm generation + compilation works
- Run
swift test to confirm tests pass
- Show which operations need implementation