Repository files navigation

GraphQL Generator for Swift

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/MyTarget/schema.graphql:

typeUser {
name: String!email: EmailAddress!
}
typeQuery {
user: User
}

2. Add the Plugin to your Target

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")
]
),

3. 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 - Defines buildGraphQLSchema function that builds an executable schema.
  • GraphQLRawSDL.swift - The graphQLRawSDL global 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 within GraphQLGenerated.

4. Create required types

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.

5. 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
letschema=trybuildGraphQLSchema(resolvers:Resolvers.self)
// Execute a query against it
letresult=tryawaitgraphql(schema: schema, request:"{ users { name email } }", context:GraphQLContext())print(result)

Configuration

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.

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 GraphQLContext and custom scalars) are namespaced inside GraphQLGenerated to 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:

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)?>}

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:

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}}

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:

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}

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:

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}

Input Object Types

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?}

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:

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.

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:

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.

About

GraphQL SDL -> Schema in Swift

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

GraphQL Generator for Swift

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/MyTarget/schema.graphql:

typeUser {
name: String!email: EmailAddress!
}
typeQuery {
user: User
}

2. Add the Plugin to your Target

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")
]
),

3. 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 - Defines buildGraphQLSchema function that builds an executable schema.
  • GraphQLRawSDL.swift - The graphQLRawSDL global 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 within GraphQLGenerated.

4. Create required types

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.

5. 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
letschema=trybuildGraphQLSchema(resolvers:Resolvers.self)
// Execute a query against it
letresult=tryawaitgraphql(schema: schema, request:"{ users { name email } }", context:GraphQLContext())print(result)

Configuration

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.

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 GraphQLContext and custom scalars) are namespaced inside GraphQLGenerated to 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:

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)?>}

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:

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}}

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:

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}

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:

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}

Input Object Types

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?}

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:

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.

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:

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.

About

GraphQL SDL -> Schema in Swift

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GraphQL Generator for Swift

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/MyTarget/schema.graphql:

typeUser {
name: String!email: EmailAddress!
}
typeQuery {
user: User
}

2. Add the Plugin to your Target

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")
]
),

3. 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 - Defines buildGraphQLSchema function that builds an executable schema.
  • GraphQLRawSDL.swift - The graphQLRawSDL global 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 within GraphQLGenerated.

4. Create required types

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.

5. 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
letschema=trybuildGraphQLSchema(resolvers:Resolvers.self)
// Execute a query against it
letresult=tryawaitgraphql(schema: schema, request:"{ users { name email } }", context:GraphQLContext())print(result)

Configuration

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.

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 GraphQLContext and custom scalars) are namespaced inside GraphQLGenerated to 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:

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)?>}

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:

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}}

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:

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}

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:

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}

Input Object Types

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?}

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:

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.

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:

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.

About

GraphQL SDL -> Schema in Swift

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GraphQL Generator for Swift

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/MyTarget/schema.graphql:

typeUser {
name: String!email: EmailAddress!
}
typeQuery {
user: User
}

2. Add the Plugin to your Target

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")
]
),

3. 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 - Defines buildGraphQLSchema function that builds an executable schema.
  • GraphQLRawSDL.swift - The graphQLRawSDL global 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 within GraphQLGenerated.

4. Create required types

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.

5. 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
letschema=trybuildGraphQLSchema(resolvers:Resolvers.self)
// Execute a query against it
letresult=tryawaitgraphql(schema: schema, request:"{ users { name email } }", context:GraphQLContext())print(result)

Configuration

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.

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 GraphQLContext and custom scalars) are namespaced inside GraphQLGenerated to 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:

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)?>}

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:

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}}

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:

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}

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:

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}

Input Object Types

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?}

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:

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.

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:

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.

About

GraphQL SDL -> Schema in Swift

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

GraphQL Generator for Swift

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/MyTarget/schema.graphql:

typeUser {
name: String!email: EmailAddress!
}
typeQuery {
user: User
}

2. Add the Plugin to your Target

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")
]
),

3. 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 - Defines buildGraphQLSchema function that builds an executable schema.
  • GraphQLRawSDL.swift - The graphQLRawSDL global 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 within GraphQLGenerated.

4. Create required types

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.

5. 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
letschema=trybuildGraphQLSchema(resolvers:Resolvers.self)
// Execute a query against it
letresult=tryawaitgraphql(schema: schema, request:"{ users { name email } }", context:GraphQLContext())print(result)

Configuration

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.

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 GraphQLContext and custom scalars) are namespaced inside GraphQLGenerated to 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:

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)?>}

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:

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}}

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:

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}

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:

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}

Input Object Types

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?}

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:

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.

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:

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.

About

GraphQL SDL -> Schema in Swift

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GraphQL Generator for Swift

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/MyTarget/schema.graphql:

typeUser {
name: String!email: EmailAddress!
}
typeQuery {
user: User
}

2. Add the Plugin to your Target

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")
]
),

3. 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 - Defines buildGraphQLSchema function that builds an executable schema.
  • GraphQLRawSDL.swift - The graphQLRawSDL global 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 within GraphQLGenerated.

4. Create required types

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.

5. 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
letschema=trybuildGraphQLSchema(resolvers:Resolvers.self)
// Execute a query against it
letresult=tryawaitgraphql(schema: schema, request:"{ users { name email } }", context:GraphQLContext())print(result)

Configuration

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.

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 GraphQLContext and custom scalars) are namespaced inside GraphQLGenerated to 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:

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)?>}

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:

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}}

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:

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}

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:

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}

Input Object Types

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?}

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:

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.

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:

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.

About

GraphQL SDL -> Schema in Swift

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

GraphQL Generator for Swift

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/MyTarget/schema.graphql:

typeUser {
name: String!email: EmailAddress!
}
typeQuery {
user: User
}

2. Add the Plugin to your Target

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")
]
),

3. 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 - Defines buildGraphQLSchema function that builds an executable schema.
  • GraphQLRawSDL.swift - The graphQLRawSDL global 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 within GraphQLGenerated.

4. Create required types

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.

5. 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
letschema=trybuildGraphQLSchema(resolvers:Resolvers.self)
// Execute a query against it
letresult=tryawaitgraphql(schema: schema, request:"{ users { name email } }", context:GraphQLContext())print(result)

Configuration

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.

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 GraphQLContext and custom scalars) are namespaced inside GraphQLGenerated to 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:

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)?>}

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:

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}}

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:

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}

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:

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}

Input Object Types

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?}

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:

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.

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:

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.

About

GraphQL SDL -> Schema in Swift

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

GraphQL Generator for Swift

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/MyTarget/schema.graphql:

typeUser {
name: String!email: EmailAddress!
}
typeQuery {
user: User
}

2. Add the Plugin to your Target

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")
]
),

3. 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 - Defines buildGraphQLSchema function that builds an executable schema.
  • GraphQLRawSDL.swift - The graphQLRawSDL global 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 within GraphQLGenerated.

4. Create required types

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.

5. 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
letschema=trybuildGraphQLSchema(resolvers:Resolvers.self)
// Execute a query against it
letresult=tryawaitgraphql(schema: schema, request:"{ users { name email } }", context:GraphQLContext())print(result)

Configuration

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.

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 GraphQLContext and custom scalars) are namespaced inside GraphQLGenerated to 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:

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)?>}

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:

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}}

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:

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}

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:

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}

Input Object Types

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?}

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:

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.

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:

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.

About

GraphQL SDL -> Schema in Swift

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages