Graphiti is a Swift library for building GraphQL schemas fast, safely and easily.
Looking for help? Find resources from the community.
An overview of GraphQL in general is available in the README for the Specification for GraphQL. That overview describes a simple set of GraphQL examples that exist as tests in this repository. A good way to get started with this repository is to walk through that README and the corresponding tests in parallel.
Add Graphiti to your Package.swift
import PackageDescription
let package = Package(
dependencies: [
.package(url: "https://github.com/GraphQLSwift/Graphiti.git", .upToNextMinor(from: "0.20.1")),
]
)
Graphiti provides two important capabilities: building a type schema, and serving queries against that type schema.
First, we declare our regular Swift entities.
struct Message : Codable {
let content: String
}
βοΈ One of the main design decisions behind Graphiti is not to polute your entities declarations. This way you can bring your entities to any other solution with ease.
Second step is to create your application's context. The context will be passed to all of your field resolver functions. This allows you to apply dependency injection to your API. This is the place where you can put code that talks to a database or another service.
struct Context {
func message() -> Message {
Message(content: "Hello, world!")
}
}
βοΈ Notice again that this step doesn't require Graphiti. It's purely business logic.
Now that we have our entities and context we can create the GraphQL API resolver.
import Graphiti
struct Resolver {
func message(context: Context, arguments: NoArguments) -> Message {
context.message()
}
}
Now we can finally define the GraphQL API with its schema.
struct MessageAPI : API {
let resolver: Resolver
let schema: Schema<Resolver, Context>
}
let api = MessageAPI(
resolver: Resolver()
schema: try! Schema<Resolver, Context> {
Type(Message.self) {
Field("content", at: \.content)
}
Query {
Field("message", at: Resolver.message)
}
}
)
Schemas may also be created in a modular way using SchemaBuilder
:
SchemaBuilder API
let builder = SchemaBuilder(Resolver.self, Context.self) builder.add( Type(Message.self) { Field("content", at: \.content) } ) builder.query.add( Field("message", at: Resolver.message) ) let schema = try builder.build() let api = MessageAPI( resolver: Resolver() schema: schema )PartialSchema implementation
final class ChatSchema: PartialSchema<Resolver, Context> { @TypeDefinitions public override var types: Types { Type(Message.self) { Field("content", at: \.content) } } @FieldDefinitions public override var query: Fields { Field("message", at: Resolver.message) } } let schema = try SchemaBuilder(Resolver.self, Context.self) .use(partials: [ChatSchema(), ...]) .build() let api = MessageAPI( resolver: Resolver() schema: schema )PartialSchema instance
let chatSchema = PartialSchema<Resolver, Context>( types: { Type(Message.self) { Field("content", at: \.content) } }, query: { Field("message", at: Resolver.message) } ) let schema = try SchemaBuilder(Resolver.self, Context.self) .use(partials: [chatSchema, ...]) .build() let api = MessageAPI( resolver: Resolver() schema: schema )
βοΈ Notice that API
allows dependency injection. You could pass mocks of resolver
and context
when testing, for example.
To query the schema we need to pass in a NIO EventLoopGroup to feed the execute function alongside the query itself.
import NIO
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
defer {
try? group.syncShutdownGracefully()
}
let result = try await api.execute(
request: "{ message { content } }",
context: Context(),
on: group
)
print(result)
The output will be:
{"data":{"message":{"content":"Hello, world!"}}}
API.execute
returns a GraphQLResult
which adopts Encodable
. You can use it with a JSONEncoder
to send the response back to the client using JSON.
Resolver functions can also be async
:
struct Resolver {
func message(context: Context, arguments: NoArguments) async -> Message {
await someAsyncMethodToGetMessage()
}
}
The resolver functions also support NIO
-style concurrency. To do so, just add one more parameter with type EventLoopGroup
to the resolver function and change the return type to EventLoopFuture<YouReturnType>
. Don't forget to import NIO.
import NIO
struct Resolver {
func message(context: Context, arguments: NoArguments, group: EventLoopGroup) -> EventLoopFuture<Message> {
group.next().makeSucceededFuture(context.message())
}
}
This library supports GraphQL subscriptions, and supports them through the Swift Concurrency AsyncThrowingStream
type. See the Usage Guide for details.
If you are unable to use Swift Concurrency, you must create a concrete subclass of the EventStream
class that implements event streaming
functionality. If you don't feel like creating a subclass yourself, you can use the GraphQLRxSwift repository
to integrate RxSwift observables out-of-the-box. Or you can use that repository as a reference to connect a different
stream library like ReactiveSwift, OpenCombine, or
one that you've created yourself.
For a progressive walkthrough, see the Usage Guide. The Star Wars API provides a fairly complete example.
This repo uses SwiftFormat, and includes lint checks to enforce these formatting standards.
To format your code, install swiftformat
and run:
swiftformat .
This project is released under the MIT license. See LICENSE for details.