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.
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
Create a .graphql file in your target's Sources directory:
Sources/MyTarget/schema.graphql:
typeUser {
name: String!email: EmailAddress!
}
typeQuery {
user: User
}In your package.swift, add the plugin and dependencies to your GraphQL target:
.target(
name: "MyTarget",
dependencies: [
.product(name: "GraphQL", package: "GraphQL"),
.product(name: "GraphQLGeneratorMacros", package: "graphql-generator"),
.product(name: "GraphQLGeneratorRuntime", package: "graphql-generator"),
],
plugins: [
.plugin(name: "GraphQLGeneratorPlugin", package: "graphql-generator")
]
),
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.
Create a type named GraphQLContext:
actorGraphQLContext{
// 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:
structResolvers:GraphQLGenerated.Resolvers{typealiasQuery=MyTarget.QuerytypealiasMutation=MyTarget.MutationtypealiasSubscription=MyTarget.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:
structQuery:GraphQLGenerated.Query{
// This is required by `GraphQLGenerated.Query`, and used by GraphQL query resolution
staticfunc user(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->(anyGraphQLGenerated.User)?{
// You can implement resolution logic however you like
return context.user
}}structUser:GraphQLGenerated.User{
// You can define the type internals however you like
letname:Stringletemail:String
// These are required by `GraphQLGenerated.User`, and used by GraphQL field resolution
func name(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->String{return name
}func email(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->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
structUser:GraphQLGenerated.User{@graphQLResolverletname:Stringletemail:String
// The `func name(...)` resolver is automatically generated.
func email(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->GraphQLScalars.EmailAddress{return.init(email:self.email)}}Note that you must include the GraphQLGeneratedMacros library to use the macros.
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
letschema=trybuildGraphQLSchema(resolvers:Resolvers.self)
// Execute a query against it
letresult=tryawaitgraphql(schema: schema, request:"{ users { name email } }", context:GraphQLContext())print(result)You can configure this package by including a graphql-generator-config.yaml file in your target's source files. It supports the following fields:
schemas: [String]?: Paths to GraphQL schema files or directories containing schema files. These paths are relative to the sources directory, and directories are explored recursively. If not provided, the whole sources directory is searched.
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.
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
GraphQL root types are generated as Swift protocols with static methods for each field.
GraphQL:
typeQuery {
user(id: ID!): Userusers: [User!]!
}
typeMutation {
upsertUser(userInfo: UserInfo!): User!
}
typeSubscription {
watchUser(id: ID!): User
}Generated Swift:
protocolQuery:Sendable{staticfunc user(id:String, context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->(anyUser)?staticfunc users(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->[anyUser]}protocolMutation:Sendable{staticfunc upsertUser(userInfo:UserInfo, context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->anyUser}protocolSubscription:Sendable{staticfunc watchUser(id:String, context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->AnyAsyncSequence<(anyUser)?>}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:
typeUser {
id: ID!name: String!email: EmailAddress!age: Int
}Generated Swift:
protocolUser:Sendable{func id(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->Stringfunc name(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->Stringfunc email(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->GraphQLScalars.EmailAddressfunc age(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->Int?}Example Implementation:
structUser:GraphQLGenerated.User{letid:Stringletname:StringletemailAddress:Stringletage:Int?func id(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->String{return id
}func name(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->String{return name
}func email(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->GraphQLScalars.EmailAddress{return.init(email: emailAddress)}func age(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->Int?{return age
}}Because these are protocols, you can have multiple implementations of the same GraphQL type (useful for testing or different data sources):
structMockUser:GraphQLGenerated.User{func id(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->String{"test-id"}func name(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->String{"Test User"}func email(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->GraphQLScalars.EmailAddress{.init(email:"test@example.com")}func age(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->Int?{nil}}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:
interfaceHasEmail {
email: EmailAddress!
}
typeUserimplementsHasEmail {
id: ID!name: String!email: EmailAddress!
}Generated Swift:
protocolHasEmail:Sendable{func email(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->GraphQLScalars.EmailAddress}protocolUser:HasEmail,Sendable{func id(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->Stringfunc name(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->Stringfunc email(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->GraphQLScalars.EmailAddress}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:
unionUserOrPost = User | PosttypeUser {
id: ID!name: String!
}
typePost {
id: ID!title: String!
}Generated Swift:
protocolUserOrPost:Sendable{}protocolUser:UserOrPost,Sendable{func id(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->Stringfunc name(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->String}protocolPost:UserOrPost,Sendable{func id(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->Stringfunc title(context:GraphQLContext, info:GraphQLResolveInfo)asyncthrows->String}GraphQL input object types are generated as concrete Swift structs with properties for each field. These are Codable and Sendable.
GraphQL:
inputUserInfo {
id: ID!name: String!email: EmailAddress!age: Introle: Role = USER
}Generated Swift:
structUserInfo:Codable,Sendable{letid:Stringletname:Stringletemail:GraphQLScalars.EmailAddressletage:Int?letrole:Role?}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:
enumRole {
ADMIN USER GUEST
}Generated Swift:
enumRole: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.
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:
scalarEmailAddresstypeUser {
email: EmailAddress!
}Required Implementation:
extensionGraphQLScalars{structEmailAddress:GraphQLScalar{letemail:Stringinit(email:String){self.email = email
}
// Codable conformance - for Swift serialization
init(from decoder:anyDecoder)throws{self.email =try decoder.singleValueContainer().decode(String.self)}func encode(to encoder:anyEncoder)throws{tryself.email.encode(to: encoder)}
// GraphQLScalar conformance - for GraphQL serialization
staticfunc serialize(this:Self)throws->Map{return.string(this.email)}staticfunc parseValue(map:Map)throws->Map{switch map {case.string:return map
default:throwGraphQLError(message:"EmailAddress cannot represent non-string value: \(map)")}}staticfunc parseLiteral(value:anyValue)throws->Map{guardlet ast = value as?StringValueelse{throwGraphQLError(
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.