Adding OpenAPI and Swagger UI endpoints
One of the most popular ways to share your OpenAPI document with your users is to host it alongside your API server itself.
Typically this is at /openapi.yaml
or similar, which serves a plain-text OpenAPI document for consumption by clients.
Additionally, you can host an HTML page that renders the OpenAPI document as interactive documentation that you can use from the browser, for example using swagger-ui.
In this tutorial we’ll add both endpoints to our Vapor server.
Add an /openapi.yaml endpoint
We’ll start with the server in its state following Generating server stubs in a Swift package (before the /emoji
endpoint is added) and we’ll add an /openapi.yaml
endpoint that serves the OpenAPI document as a static resource.
In the server package, create a
Public/
directory for serving static content.console
server-openapi-endpoints.console.0.txt% mkdir Public
Move
openapi.yaml
fromSources/
and create a symlink to it back in theSources/
directory.console
server-openapi-endpoints.console.1.txt% mkdir Public % mv Sources/openapi.yaml Public/ % ln -s ../Public/openapi.yaml Sources/openapi.yaml
In
main.swift
, Add a Vapor middleware that serves the contents of thePublic/
directory.main.swift
server-openapi-endpoints.main.1.swiftimport Foundation import Vapor import OpenAPIRuntime import OpenAPIVapor // Define a type that conforms to the generated protocol. struct GreetingServiceAPIImpl: APIProtocol { func getGreeting( _ input: Operations.getGreeting.Input ) async throws -> Operations.getGreeting.Output { let name = input.query.name ?? "Stranger" let greeting = Components.Schemas.Greeting(message: "Hello, \(name)!") return .ok(.init(body: .json(greeting))) } } // Create your Vapor application. let app = Vapor.Application() // Create a VaporTransport using your application. let transport = VaporTransport(routesBuilder: app) // Create an instance of your handler type that conforms the generated protocol // defining your service API. let handler = GreetingServiceAPIImpl() // Call the generated function on your implementation to add its request // handlers to the app. try handler.registerHandlers(on: transport, serverURL: Servers.Server1.url()) // Add Vapor middleware to serve the contents of the Public/ directory. app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) // Start the app as you would normally. try await app.execute()
From the
Product
menu, selectScheme > Edit Scheme...
. WithRun
selected in the sidebar, select theOptions
tab and check the box labeledUse custom working directory
and use the path containing Package.swift for this package.This step is necessary because the Vapor middleware serves files relative to the current working directory for the running server process.
Test this endpoint in your browser, or using curl.
console
server-openapi-endpoints.console.2.txt% mkdir Public % mv Sources/openapi.yaml Public/ % ln -s ../Public/openapi.yaml Sources/openapi.yaml % curl "localhost:8080/openapi.yaml" openapi: '3.1.0' info: title: GreetingService version: 1.0.0 servers: - url: https://example.com/api description: Example service deployment. paths: /greet: get: operationId: getGreeting parameters: - name: name ---[SNIP]---
Add a Swagger UI endpoint
Now we’ll add a static openapi.html
page that serves Swagger UI and add a redirect to this page from /openapi
for discoverability.
Create the file
Public/openapi.html
with the HTML contents as shown on the right.By placing it in the public directory, it is already reachable at
/openapi.html
.openapi.html
server-openapi-endpoints.openapi.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" type="text/css" href="//unpkg.com/swagger-ui-dist@5/swagger-ui.css"> <title>Swift OpenAPI Sample API</title> <body> <div id="openapi" /> <script src="//unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script> <script> window.onload = function() { const ui = SwaggerUIBundle({ url: "openapi.yaml", dom_id: "#openapi", deepLinking: true, validatorUrl: "none" }) } </script> </body>
Add a redirect for
/openapi
toopenapi.html
, which serves the rendered documentation.main.swift
server-openapi-endpoints.main.2.swiftimport Foundation import Vapor import OpenAPIRuntime import OpenAPIVapor // Define a type that conforms to the generated protocol. struct GreetingServiceAPIImpl: APIProtocol { func getGreeting( _ input: Operations.getGreeting.Input ) async throws -> Operations.getGreeting.Output { let name = input.query.name ?? "Stranger" let greeting = Components.Schemas.Greeting(message: "Hello, \(name)!") return .ok(.init(body: .json(greeting))) } } // Create your Vapor application. let app = Vapor.Application() // Create a VaporTransport using your application. let transport = VaporTransport(routesBuilder: app) // Create an instance of your handler type that conforms the generated protocol // defining your service API. let handler = GreetingServiceAPIImpl() // Call the generated function on your implementation to add its request // handlers to the app. try handler.registerHandlers(on: transport, serverURL: Servers.Server1.url()) // Add Vapor middleware to serve the contents of the Public/ directory. app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) // Redirect /openapi to openapi.html, which serves the rendered documentation. app.get("openapi") { $0.redirect(to: "/openapi.html", redirectType: .permanent) } // Start the app as you would normally. try await app.execute()
Add a relative server URL to the OpenAPI document.
This allows you to use the rendered documentation to make requests to the instance serving the OpenAPI document itself.
main.swift
server-openapi-endpoints.openapi.1.yamlopenapi: '3.1.0' info: title: GreetingService version: 1.0.0 servers: - url: https://example.com/api description: Example service deployment. - url: /api description: The server hosting this document. paths: /greet: get: operationId: getGreeting parameters: - name: name required: false in: query description: The name used in the returned greeting. schema: type: string responses: '200': description: A success response with a greeting. content: application/json: schema: $ref: '#/components/schemas/Greeting' components: schemas: Greeting: type: object properties: message: type: string required: - message
Visit
http://localhost:8080/openapi
in your browser which should show the rendered documentation for the OpenAPI document.