Streaming is available in most browsers,
and in the Developer app.
-
Explore the Swift on Server ecosystem
Swift is a great language for writing your server applications, and powers critical services across Apple's cloud products. We'll explore tooling, delve into the Swift server package ecosystem, and demonstrate how to interact with databases and add observability to applications.
Chapters
- 0:00 - Introduction
- 0:13 - Agenda
- 0:27 - Meet Swift on Server
- 2:30 - Build a service
- 3:46 - Swift OpenAPI generator
- 5:42 - Database drivers
- 10:53 - Observability
- 15:19 - Explore the ecosystem
- 16:12 - Wrap up
Resources
Related Videos
WWDC23
-
DownloadArray
-
-
3:23 - EventService Package.swift
// swift-tools-version:5.9 import PackageDescription let package = Package( name: "EventService", platforms: [.macOS(.v14)], dependencies: [ .package( url: "https://github.com/apple/swift-openapi-generator", from: "1.2.1" ), .package( url: "https://github.com/apple/swift-openapi-runtime", from: "1.4.0" ), .package( url: "https://github.com/vapor/vapor", from: "4.99.2" ), .package( url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.1" ), ], targets: [ .target( name: "EventAPI", dependencies: [ .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), ], plugins: [ .plugin( name: "OpenAPIGenerator", package: "swift-openapi-generator" ) ] ), .executableTarget( name: "EventService", dependencies: [ "EventAPI", .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), .product( name: "OpenAPIVapor", package: "swift-openapi-vapor" ), .product( name: "Vapor", package: "vapor" ), ] ), ] )
-
4:05 - EventService openapi.yaml
openapi: "3.1.0" info: title: "EventService" version: "1.0.0" servers: - url: "https://localhost:8080/api" description: "Example service deployment." paths: /events: get: operationId: "listEvents" responses: "200": description: "A success response with all events." content: application/json: schema: type: "array" items: $ref: "#/components/schemas/Event" post: operationId: "createEvent" requestBody: description: "The event to create." required: true content: application/json: schema: $ref: '#/components/schemas/Event' responses: '201': description: "A success indicating the event was created." '400': description: "A failure indicating the event wasn't created." components: schemas: Event: type: "object" description: "An event." properties: name: type: "string" description: "The event's name." date: type: "string" format: "date" description: "The day of the event." attendee: type: "string" description: "The name of the person attending the event." required: - "name" - "date" - "attendee"
-
4:35 - EventService initial implementation
import OpenAPIRuntime import OpenAPIVapor import Vapor import EventAPI @main struct Service { static func main() async throws { let application = try await Vapor.Application.make() let transport = VaporTransport(routesBuilder: application) let service = Service() try service.registerHandlers( on: transport, serverURL: URL(string: "/api")! ) try await application.execute() } } extension Service: APIProtocol { func listEvents( _ input: Operations.listEvents.Input ) async throws -> Operations.listEvents.Output { let events: [Components.Schemas.Event] = [ .init(name: "Server-Side Swift Conference", date: "26.09.2024", attendee: "Gus"), .init(name: "Oktoberfest", date: "21.09.2024", attendee: "Werner"), ] return .ok(.init(body: .json(events))) } func createEvent( _ input: Operations.createEvent.Input ) async throws -> Operations.createEvent.Output { return .undocumented(statusCode: 501, .init()) } }
-
6:56 - EventService Package.swift
// swift-tools-version:5.9 import PackageDescription let package = Package( name: "EventService", platforms: [.macOS(.v14)], dependencies: [ .package( url: "https://github.com/apple/swift-openapi-generator", from: "1.2.1" ), .package( url: "https://github.com/apple/swift-openapi-runtime", from: "1.4.0" ), .package( url: "https://github.com/vapor/vapor", from: "4.99.2" ), .package( url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.1" ), .package( url: "https://github.com/vapor/postgres-nio", from: "1.19.1" ), ], targets: [ .target( name: "EventAPI", dependencies: [ .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), ], plugins: [ .plugin( name: "OpenAPIGenerator", package: "swift-openapi-generator" ) ] ), .executableTarget( name: "EventService", dependencies: [ "EventAPI", .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), .product( name: "OpenAPIVapor", package: "swift-openapi-vapor" ), .product( name: "Vapor", package: "vapor" ), .product( name: "PostgresNIO", package: "postgres-nio" ), ] ), ] )
-
7:08 - Implementing the listEvents method
import OpenAPIRuntime import OpenAPIVapor import Vapor import EventAPI import PostgresNIO @main struct Service { let postgresClient: PostgresClient static func main() async throws { let application = try await Vapor.Application.make() let transport = VaporTransport(routesBuilder: application) let postgresClient = PostgresClient( configuration: .init( host: "localhost", username: "postgres", password: nil, database: nil, tls: .disable ) ) let service = Service(postgresClient: postgresClient) try service.registerHandlers( on: transport, serverURL: URL(string: "/api")! ) try await withThrowingDiscardingTaskGroup { group in group.addTask { await postgresClient.run() } group.addTask { try await application.execute() } } } } extension Service: APIProtocol { func listEvents( _ input: Operations.listEvents.Input ) async throws -> Operations.listEvents.Output { let rows = try await self.postgresClient.query("SELECT name, date, attendee FROM events") var events = [Components.Schemas.Event]() for try await (name, date, attendee) in rows.decode((String, String, String).self) { events.append(.init(name: name, date: date, attendee: attendee)) } return .ok(.init(body: .json(events))) } func createEvent( _ input: Operations.createEvent.Input ) async throws -> Operations.createEvent.Output { return .undocumented(statusCode: 501, .init()) } }
-
9:02 - Implementing the createEvent method
func createEvent( _ input: Operations.createEvent.Input ) async throws -> Operations.createEvent.Output { switch input.body { case .json(let event): try await self.postgresClient.query( """ INSERT INTO events (name, date, attendee) VALUES (\(event.name), \(event.date), \(event.attendee)) """ ) return .created(.init()) } }
-
11:34 - Instrumenting the listEvents method
func listEvents( _ input: Operations.listEvents.Input ) async throws -> Operations.listEvents.Output { let logger = Logger(label: "ListEvents") logger.info("Handling request", metadata: ["operation": "\(Operations.listEvents.id)"]) Counter(label: "list.events.counter").increment() return try await withSpan("database query") { span in let rows = try await postgresClient.query("SELECT name, date, attendee FROM events") return try await .ok(.init(body: .json(decodeEvents(rows)))) } }
-
13:14 - EventService Package.swift
// swift-tools-version:5.9 import PackageDescription let package = Package( name: "EventService", platforms: [.macOS(.v14)], dependencies: [ .package( url: "https://github.com/apple/swift-openapi-generator", from: "1.2.1" ), .package( url: "https://github.com/apple/swift-openapi-runtime", from: "1.4.0" ), .package( url: "https://github.com/vapor/vapor", from: "4.99.2" ), .package( url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.1" ), .package( url: "https://github.com/vapor/postgres-nio", from: "1.19.1" ), .package( url: "https://github.com/apple/swift-log", from: "1.5.4" ), ], targets: [ .target( name: "EventAPI", dependencies: [ .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), ], plugins: [ .plugin( name: "OpenAPIGenerator", package: "swift-openapi-generator" ) ] ), .executableTarget( name: "EventService", dependencies: [ "EventAPI", .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), .product( name: "OpenAPIVapor", package: "swift-openapi-vapor" ), .product( name: "Vapor", package: "vapor" ), .product( name: "PostgresNIO", package: "postgres-nio" ), .product( name: "Logging", package: "swift-log" ), ] ), ] )
-
13:38 - Adding logging to the createEvent method
func createEvent( _ input: Operations.createEvent.Input ) async throws -> Operations.createEvent.Output { switch input.body { case .json(let event): do { try await self.postgresClient.query( """ INSERT INTO events (name, date, attendee) VALUES (\(event.name), \(event.date), \(event.attendee)) """ ) return .created(.init()) } catch let error as PSQLError { let logger = Logger(label: "CreateEvent") if let message = error.serverInfo?[.message] { logger.info( "Failed to create event", metadata: ["error.message": "\(message)"] ) } return .badRequest(.init()) } } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.