This is a Swift package plugin that generates server-side GraphQL API code from GraphQL schema files, inspired by GraphQL Tools' makeExecutableSchema and Swift's OpenAPI Generator.
Using this package has the following benefits:
- Guarantees conformance with the declared GraphQL spec
- Leverages Swift's type system for compile-time safety
- Flexiblity in backing data types
- Generates all the piping between Swift and GraphQL - you just write the resolvers
To expose your schema through HTTP, check out graphql-vapor or graphql-hummingbird. To define your schema in native Swift, use Graphiti instead.
Usage
Take a look at the example projects to see real, fully featured implementations:
- HelloWorldServer - Demonstrates all GraphQL type mappings with a comprehensive schema
- StarWars - A production-like example using the SWAPI with DataLoader for caching
1. Create a GraphQL Schema
Create a .graphql file in your target's Sources directory:
Sources/ExamplePackage/schema.graphql:
type User { name: String! email: EmailAddress! } type Query { user: User }
2. Build Your Project
When you build, the plugin will automatically generate Swift code. If you want, you can view it in the .build/plugins/outputs directory:
BuildGraphQLSchema.swift- DefinesbuildGraphQLSchemafunction that builds an executable schema.GraphQLRawSDL.swift- ThegraphQLRawSDLglobal property, which is a Swift string literal of the input schema. This is used at runtime to parse the schema.GraphQLTypes.swift- Swift protocols and types for your GraphQL types. These are all namespaced withinGraphQLGenerated.
3. Create required types
Create a type named GraphQLContext:
actor GraphQLContext { // Add any features you like }
If your schema has any custom scalar types, you must create them manually in the GraphQLScalars namespace. See the Scalars section below for details.
Create a struct that conforms to GraphQLGenerated.Resolvers by defining the required typealiases:
struct Resolvers: GraphQLGenerated.Resolvers { typealias Query = ExamplePackage.Query typealias Mutation = ExamplePackage.Mutation typealias Subscription = ExamplePackage.Subscription }
As you build the Query, Mutation, and Subscription types and their resolution logic, you will be forced to define a concrete type for every reachable GraphQL type, according to its generated protocol:
struct Query: GraphQLGenerated.Query { // This is required by `GraphQLGenerated.Query`, and used by GraphQL query resolution static func user(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> (any GraphQLGenerated.User)? { // You can implement resolution logic however you like return context.user } } struct User: GraphQLGenerated.User { // You can define the type internals however you like let name: String let email: String // These are required by `GraphQLGenerated.User`, and used by GraphQL field resolution func name(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String { return name } func email(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> GraphQLScalars.EmailAddress { // You can implement resolution logic however you like return .init(email: self.email) } }
Let the protocol conformance guide you on what resolver methods your types must define, and keep going until everything compiles.
This package also provides a @graphQLResolver macro to reduce boilerplate in cases where the resolver simply results in the value of a Swift property. For example, the User type above could be shortened to:
import GraphQLGeneratedMacros struct User: GraphQLGenerated.User { @graphQLResolver let name: String let email: String // The `func name(...)` resolver is automatically generated. func email(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> GraphQLScalars.EmailAddress { return .init(email: self.email) } }
Note that you must include the GraphQLGeneratedMacros library to use the macros.
4. Execute GraphQL Queries
You're done! You can now instantiate your GraphQL schema by calling buildGraphQLSchema, and run queries against it:
import GraphQL // Build the auto-generated schema let schema = try buildGraphQLSchema(resolvers: Resolvers.self) // Execute a query against it let result = try await graphql(schema: schema, request: "{ users { name email } }", context: GraphQLContext()) print(result)
Design Philosophy
This generator is designed with the following guiding principles:
- Protocol-based flexibility: GraphQL types are generated as Swift protocols (except where concrete types are needed), allowing you to implement backing types however you want - structs, actors, classes, or any combination.
- Explicit over implicit: No default resolvers based on reflection. While more verbose, this provides better performance and clearer schema evolution handling. Macros are provided for common boilerplate.
- Type safety: Leverage Swift's type system to ensure compile-time conformance with your GraphQL schema.
- Namespace isolation: All generated types (except
GraphQLContextand custom scalars) are namespaced insideGraphQLGeneratedto avoid polluting your package's type namespace.
GraphQL to Swift Type Mappings
This section describes how each GraphQL type is converted to Swift code, with concrete examples from the HelloWorldServer example. Note that all generated types are namespaced inside GraphQLGenerated
Root Types (Query, Mutation, Subscription)
GraphQL root types are generated as Swift protocols with static methods for each field.
GraphQL:
type Query { user(id: ID!): User users: [User!]! } type Mutation { upsertUser(userInfo: UserInfo!): User! } type Subscription { watchUser(id: ID!): User }
Generated Swift:
protocol Query: Sendable { static func user(id: String, context: GraphQLContext, info: GraphQLResolveInfo) async throws -> (any User)? static func users(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> [any User] } protocol Mutation: Sendable { static func upsertUser(userInfo: UserInfo, context: GraphQLContext, info: GraphQLResolveInfo) async throws -> any User } protocol Subscription: Sendable { static func watchUser(id: String, context: GraphQLContext, info: GraphQLResolveInfo) async throws -> AnyAsyncSequence<(any User)?> }
Object Types
GraphQL object types are generated as Swift protocols with instance methods for each field. This allows for flexible implementations - you can use structs, actors, classes, or any other type that conforms to the protocol.
GraphQL:
type User { id: ID! name: String! email: EmailAddress! age: Int }
Generated Swift:
protocol User: Sendable { func id(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String func name(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String func email(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> GraphQLScalars.EmailAddress func age(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> Int? }
Example Implementation:
struct User: GraphQLGenerated.User { let id: String let name: String let emailAddress: String let age: Int? func id(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String { return id } func name(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String { return name } func email(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> GraphQLScalars.EmailAddress { return .init(email: emailAddress) } func age(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> Int? { return age } }
Because these are protocols, you can have multiple implementations of the same GraphQL type (useful for testing or different data sources):
struct MockUser: GraphQLGenerated.User { func id(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String { "test-id" } func name(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String { "Test User" } func email(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> GraphQLScalars.EmailAddress { .init(email: "test@example.com") } func age(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> Int? { nil } }
Interface Types
GraphQL interfaces are generated as Swift protocols with required methods for each field. Types implementing the interface will have their protocol marked as conforming to the interface protocol.
GraphQL:
interface HasEmail { email: EmailAddress! } type User implements HasEmail { id: ID! name: String! email: EmailAddress! }
Generated Swift:
protocol HasEmail: Sendable { func email(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> GraphQLScalars.EmailAddress } protocol User: HasEmail, Sendable { func id(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String func name(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String func email(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> GraphQLScalars.EmailAddress }
Union Types
GraphQL union types are generated as Swift marker protocols with no required properties or methods. Union member types have their protocols marked as conforming to the union protocol.
GraphQL:
union UserOrPost = User | Post type User { id: ID! name: String! } type Post { id: ID! title: String! }
Generated Swift:
protocol UserOrPost: Sendable {} protocol User: UserOrPost, Sendable { func id(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String func name(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String } protocol Post: UserOrPost, Sendable { func id(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String func title(context: GraphQLContext, info: GraphQLResolveInfo) async throws -> String }
Input Object Types
GraphQL input object types are generated as concrete Swift structs with properties for each field. These are Codable and Sendable.
GraphQL:
input UserInfo { id: ID! name: String! email: EmailAddress! age: Int role: Role = USER }
Generated Swift:
struct UserInfo: Codable, Sendable { let id: String let name: String let email: GraphQLScalars.EmailAddress let age: Int? let role: Role? }
Enum Types
GraphQL enum types are generated as concrete Swift enums with raw String values. Each GraphQL enum case becomes a Swift enum case with its raw value matching the GraphQL case name.
GraphQL:
enum Role { ADMIN USER GUEST }
Generated Swift:
enum Role: String, Codable, Sendable { case admin = "ADMIN" case user = "USER" case guest = "GUEST" }
These generated enums can be used directly in your code without any additional implementation.
Scalar Types
GraphQL scalar types are not generated by the plugin. Instead, they are referenced as GraphQLScalars.<name>, and you must define the type and conform it to GraphQLScalar.
GraphQL:
scalar EmailAddress type User { email: EmailAddress! }
Required Implementation:
extension GraphQLScalars { struct EmailAddress: GraphQLScalar { let email: String init(email: String) { self.email = email } // Codable conformance - for Swift serialization init(from decoder: any Decoder) throws { self.email = try decoder.singleValueContainer().decode(String.self) } func encode(to encoder: any Encoder) throws { try self.email.encode(to: encoder) } // GraphQLScalar conformance - for GraphQL serialization static func serialize(this: Self) throws -> Map { return .string(this.email) } static func parseValue(map: Map) throws -> Map { switch map { case .string: return map default: throw GraphQLError(message: "EmailAddress cannot represent non-string value: \(map)") } } static func parseLiteral(value: any Value) throws -> Map { guard let ast = value as? StringValue else { throw GraphQLError( message: "EmailAddress cannot represent non-string value: \(print(ast: value))", nodes: [value] ) } return .string(ast.value) } } }
Ensure that your Codable and GraphQLScalar conformances agree on the same representation format.