Open-source project
GraphQLSwift/GraphQL avatar
GraphQLSwift/GraphQL

GraphQLSwift/GraphQL is a Swift port of the JavaScript reference implementation

The Swift GraphQL implementation for macOS and Linux

958 stars79 forksSwiftMIT

At a glance

What is it?
GraphQLSwift/GraphQL gives Swift and macOS or Linux code a GraphQL engine that mirrors graphql-js rather than inventing a new dialect. It gives you schema construction, execution and subscriptions, while HTTP exposure and schema generation are delegated to separate packages.
Who is it for?
Adopt GraphQLSwift/GraphQL when your server side is Swift and you want behaviour that tracks the JavaScript reference implementation, and you are willing to hold a port rather than the canonical library. Skip it if you need the HTTP layer or the schema generator, since the README points to graphql-vapor, graphql-hummingbird, Graphiti and graphql-generator for those parts instead.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 23 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A port that mirrors graphql-js file by file

GraphQLSwift/GraphQL is the Swift implementation of GraphQL for macOS and Linux. It is not an independent design. The project states that the repository is a Swift port of the JavaScript reference implementation, and it goes further than a general claim: the source layout is said to mirror the structure of the canonical GraphQL implementation written in JavaScript and TypeScript, with a file for file correspondence.

That mirroring has a practical consequence. When a feature is missing, the project suggests looking at the original code and translating it to Swift, which is most of the time. The example given is the abstract syntax tree file, Sources/GraphQL/Language/AST.swift in the Swift tree and src/language/ast.js in the JavaScript tree. For a team that already knows graphql-js, the reading curve is short and the surprises are fewer.

The project is hosted under the GraphQLSwift organisation alongside sibling packages that handle the parts this repository does not: graphql-generator and Graphiti for creating schemas, graphql-vapor and graphql-hummingbird for exposing a schema over HTTP. The badge in the readme places the package at the SSWG incubating level, and the topic list names swift, swift-nio, swiftpm, vapor and graphiti. The license is MIT.

Schemas are Swift values built from GraphQLObjectType and GraphQLField

The type system in this package is expressed as ordinary Swift values rather than a separate schema language, which is the main design choice that affects how you work with it. A GraphQLSchema is constructed in code, and its query, mutation and subscription roots are GraphQLObjectType instances whose fields are GraphQLField values carrying a type, arguments and resolver closures.

The smallest possible schema looks like this. The query root is named Query, the field hello resolves to the string world, and the resolver is a closure taking four parameters.

swift
let schema = try GraphQLSchema(
    query: GraphQLObjectType(                   // Defines the special "query" type
        name: "Query",
        fields: [
            "hello": GraphQLField(              // Users may query 'hello'
                type: GraphQLString,            // The result is a string type
                resolve: { _, _, _, _ in
                    "world"                     // The result of querying 'hello' is "world"
                }
            )
        ]
    )
)

For anything larger the readme points you at the test files, which is the honest advice given that a full schema in Swift source is verbose. The trade-off is that you get compile time checking on the shape of your types and no schema first workflow, so any tool that needs to read your schema as text will need the generator packages instead of parsing this source.

Running a query is a single call to the global graphql function

Once a schema exists, execution goes through the global graphql function. It takes the schema and a request string, it is asynchronous, and it throws, so the call site is marked try await.

swift
let result = try await graphql(
    schema: schema,
    request: "{ hello }"
)

The value that comes back is a GraphQLResult, and encoding it produces a JSON object with the resolved fields.

json
{ "hello": "world" }

The request argument is the GraphQL query text, so the same entry point serves queries written by hand and queries arriving over a transport. The library does not decide how the text arrives. That division is the reason the HTTP packages exist as separate repositories, and it also means you can drive the engine from a test, a CLI or an in-process handler without pulling in a web framework.

One thing the readme does not document is how the resolver closure signature evolves across major versions. Each resolver takes four arguments, and the names are discarded with underscores in the examples, so the shape of the tuple is something you learn from the compiler rather than from prose.

Subscriptions require a subscribe resolver that returns an AsyncSequence

Subscription support is built into the package, and the API is shaped by Swift concurrency rather than by an observer pattern. A subscription field needs two closures. The subscribe resolver constructs the event stream and returns any type conforming to AsyncSequence whose element type is Sendable. The resolve closure transforms each event as it arrives and returns the field result type.

The schema half of the example looks like this, with the event stream built from a repeating timer.

swift
let schema = try GraphQLSchema(
    subscribe: GraphQLObjectType(
        name: "Subscribe",
        fields: [
            "hello": GraphQLField(
                type: GraphQLString,
                resolve: { eventResult, _, _, _ in       // Defines how to transform each event when it occurs
                    return eventResult
                },
                subscribe: { _, _, _, _ in               // Defines how to construct the event stream
                    return AsyncThrowingStream<String, Error> { continuation in

The rest of that stream yields a value every three seconds and continues as the subscription lives.

swift
                        let timer = Timer.scheduledTimer(
                            withTimeInterval: 3,
                            repeats: true,
                        ) {
                            continuation.yield("world")  // Emits "world" every 3 seconds
                        }
                    }
                )
            )
        ]
    )
)

Execution uses a separate function, graphqlSubscribe, and the result is unwrapped before you iterate.

swift
let subscriptionResult = try await graphqlSubscribe(
    schema: schema,
)
let stream = subscriptionResult.get()
for try await result in stream {
    print(result)
}

The Sendable requirement on the element type is the constraint to plan around. It pushes event payloads toward value types and moves the care about thread safety onto the producer, and it also means a subscription that wants to emit reference type objects has to think about that before it compiles.

Field order breaks under JSONEncoder, which is why GraphQLJSONEncoder exists

This is the sharpest edge in the package and the one most likely to bite you quietly. If you encode a GraphQLResult with an ordinary JSONEncoder, the readme says there are no guarantees that the field order will match the query, and that doing so violates the GraphQL spec on serialized map ordering. Nothing throws. The response is valid JSON with the right fields in an order the client did not ask for.

The spec reference given is the June 2018 edition, section on serialized map ordering. If your clients are order sensitive, for instance because they diff two responses or a test asserts on the raw body, this is a real defect rather than a cosmetic one.

The fix ships with the package. GraphQLResult should be encoded with GraphQLJSONEncoder, which is shipped here for that purpose. The lesson for anyone integrating this engine is that you have to know about the encoder on day one. A response that looks correct in a manual test can reorder its fields the moment you add another field to the selection set, and the cause will not be obvious from the failing assertion.

The broader lesson is about porting. A behaviour that the JavaScript implementation gets right by construction can be a footgun in Swift, because Swift gives you a different default encoder rather than the reference implementation's serializer.

Adding the package and keeping the source formatted

The readme does not give an installation section, so there is no install command to copy. What the repository does show is a Package.swift at the root, which is the Swift Package Manager manifest, and a badge linking to the package listing on Swift Package Index at swiftpackageindex.com/GraphQLSwift/GraphQL. The package description names macOS and Linux as the target platforms.

The one command the project does document is for formatting, and it is a repository maintenance step rather than a consumer step.

bash
swift format --parallel --in-place --recursive ./

The project states that it uses the standard swift format tooling and that the repository includes lint checks enforcing those formatting standards. Contributors are also expected to follow the Swift Code of Conduct, the Swift API Design Guidelines and the SSWG technical best practices. Those two guideline documents matter more than they look, because the API design guidelines are what shape the shape of the public types you will be calling.

For security issues there is a SECURITY.md with a reporting path, and MIGRATION.md exists for people moving between major versions.

What this package refuses to do, and where the alternatives start

Two gaps are visible in the readme itself, and both are deliberate hand-offs rather than omissions.

The first is HTTP. There is no server, no route, no request decoding and no response writing in this repository. The readme points to graphql-vapor and graphql-hummingbird for that, both from the same organisation, one oriented toward the Vapor ecosystem and one toward Hummingbird. The difference in approach is that the engine and the transport are separate packages, so the choice of web framework does not force you to leave the GraphQL implementation, and the framework packages are where the version compatibility questions live.

The second is schema generation. GraphQLSchema objects in Swift source are verbose, so the readme recommends graphql-generator or Graphiti to create them more easily. These are separate packages that exist to make schema creation easier than hand writing type definitions in Swift source.

The third limitation is about versioning rather than scope. The project aims to support the previous three Swift versions, and MIGRATION.md holds the notes for upgrading to new major versions. Since this is a port of a moving reference implementation, the Swift package versions on your dependency list will track upstream GraphQL changes over time, and a major bump is a real upgrade project rather than a routine pull.

Release cadence and what a port costs you over time

The recent release history is steady and the naming convention is unusual. Version 4.1.2 was published on 2026-06-25, version 4.2.0 on 2026-07-08, and version 4.3.0 on 2026-09-30 under the name Kourou, following Puerto Madryn for 4.2.0 and Tacuarembó for 4.1.2. The repository is not archived and the last push was on 2026-09-30.

For a port, the interesting question is not the release rate but what each release tracks. Because the goal is parity with the JavaScript reference implementation, a version bump usually means the port caught up with upstream changes, which can mean behaviour differences in your API surface even when your application code is untouched. The MIT license keeps the legal side simple, with no copyleft obligations for a service you host.

The costs that are not captured in release notes are the costs of the port model itself. You are tracking a second implementation of a specification, and when a query behaves differently from graphql-js you will be debugging your port rather than the language. The reverse is also true: the project explicitly says that missing features can usually be translated from the original code, so the escape hatch when something is absent is to port that piece yourself and then keep it in sync by hand.

The Benchmarks directory in the repository shows that performance work has a place in the project, but the readme does not describe how to run those benchmarks or what they measure, so treat any performance expectation for this package as unverified.

Editorial conclusion

Adopt GraphQLSwift/GraphQL when your server side is Swift and you want behaviour that tracks the JavaScript reference implementation, and you are willing to hold a port rather than the canonical library. Skip it if you need the HTTP layer or the schema generator, since the README points to graphql-vapor, graphql-hummingbird, Graphiti and graphql-generator for those parts instead. Before you commit, check the MIGRATION.md notes for the major version you are moving to, and confirm that GraphQLJSONEncoder is used wherever the serialized map ordering of the spec matters to your clients.

Frequently asked questions

How do I add GraphQLSwift/GraphQL to a Swift project?

The readme does not document an install step, so it points to the package listing on Swift Package Index for GraphQLSwift/GraphQL. The repository contains a Package.swift manifest at its root, which is the standard Swift Package Manager manifest, and the package targets macOS and Linux.

Is GraphQLSwift/GraphQL the same as graphql-js?

No, it is a Swift port of it. The project states that the repository is a Swift port of the JavaScript reference implementation and mirrors its structure, and that a missing feature can usually be translated from the original code.

How do I expose a GraphQL schema over HTTP?

This package does not handle HTTP. The readme points to graphql-vapor or graphql-hummingbird from the same organisation, and to graphql-generator or Graphiti for creating schemas more easily.

Why do my GraphQL response fields come back in the wrong order?

Encoding a GraphQLResult with an ordinary JSONEncoder gives no guarantee that the field order matches the query, which the readme says violates the GraphQL spec on serialized map ordering. Use the GraphQLJSONEncoder that ships with the package to preserve the order.

Which Swift versions does GraphQLSwift/GraphQL support?

The project states that the package aims to support the previous three Swift versions. Details for upgrading across major versions live in MIGRATION.md, and the most recent release was 4.3.0 on 2026-09-30.

Does GraphQLSwift/GraphQL support subscriptions?

Yes. A subscription field needs a subscribe resolver returning an AsyncSequence with a Sendable element type, plus a resolve closure that transforms each event. The graphqlSubscribe function executes a subscription and the result is iterated as an async sequence.

Official sources

  1. GraphQLSwift/GraphQL on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/graphqlswift-graphql.svg)](https://hysenlabs.com/projects/graphqlswift-graphql)

Was this useful?