Typed Documents
Although GraphQL defines conventions and guarantees for the client-side GraphQL query language and the server-side GraphQL type system, it’s still ultimately an API specifaction for client-server applications.
In GraphQL, we create schemas that describe the type system that queries are executed against, and said schema describes a shape of GraphQL types and scalars. As such, even if we use strong types on the client-side and strong types on the server-side, we still have to bridge the gap between both ends.
Schemas and Queries
Given a GraphQL schema, expressed here in the Schema Definition Language (“SDL”), in its simplest form, we define fields on objects that, when queried, may resolve to scalar values.
When querying this schema we may write a query that requests our defined fields:
Simplifying this, a GraphQL query, which has been validated against a GraphQL schema, matches a subset of the structure our GraphQL schema defines. However, while it “selects” fields and defines how to execute their resolvers, type information is only present on the schema.
As such, as per the specification, a GraphQL API with the above SDL may only return data matching our query, such as:
It’s the server-side type system’s responsibility to ensure that when this schema is executed against a valid query, that the execution result matches the types defined.
As such, while the server-side, written in any library for any language implementing the GraphQL specification, has complete knowledge of the schemas types and structure, queries are subject to these types implicitly.
Type Generation
If we’re using TypeScript on the client-side and have a set of GraphQL documents that we may execute against our schema, we can only know the documents’ result types by looking comparing them to the schema.
In GraphQL, this is often done ahead of time in with “Type Generation”. This means that we input our schema and our queries into a process at compile-time and convert the shape of the query to TypeScript’s type system.
As such, the shape of data in the queries above would match a TypeScript type looking like the following:
In many tools this is done using code generation, a process that, like many concepts in GraphQL, has already been established with JSON Schemas, or other API-shape specifications. In code generation, we would have to ensure that a compile-time tool generates or connects our TypeScript type to what we use at runtime — a query string or AST.
With gql.tada
, this all happens in the TypeScript type system
and is more invisible since no files are automatically
generated per GraphQL document.
Integration with Clients
If we don’t integrate GraphQL on the client-side with Type Generation, then a minimal example using GraphQL is quite simple.
In this example, we’ll have a GraphQL query sent to an API using a simple
fetch
call:
In TypeScript however, we’re lacking two vital integration points here.
Without Type Generations, neither data
nor variables
are typed
according to our GraphQL schema, although with GraphQL’s guarantees these
types should be unambiguous.
Manual Type Generation
With manual code generation tools, a separate tool would output a file
containing our query’s types. For instance, it may output a separate file
that contains the Result
and Variables
types we need:
However, this now requires us to make an effort to include these types
manually in our execute
function:
This is a very manual process though that doesn’t match our expectation that GraphQL is strongly typed and types should hence be inferred implicitly.
TypedDocumentNode
types
What we really wish to do with client-side GraphQL is to attach our generated types
to the DocumentNode
type directly. This would mean that our query
above can only
lead to the correct types being used. Furthermore, by using said query, its types
could be inferred automatically.
Instead of having a DocumentNode
type, ideally, we’d want the query to be typed
as TypedDocumentNode<Result, Variables>
.
As a result, GraphQL in TypeScript has two ways of attaching types to GraphQL documents:
- the
@graphql-typed-document-node/core
type - the
graphql
package’s type
Our type generation tools can now output a TypedDocumentNode
that has types attached
to it directly:
Which for clients executing a GraphQL query means, that they can infer the types
of a given GraphQL query by matching it against TypedDocumentNode
instead.
In our example, we can now infer the generic types from the query
instead:
gql.tada
type inference
Having types output by a separate code generation tool again doesn’t quite match our expectation that GraphQL is strongly typed and types should hence be inferred implicitly.
In gql.tada
however, the idea is that we get from writing a query to having a
TypedDocumentNode
type just via TypeScript inference, without running a separate
tool or having files be generated for each query.
In essence, what gql.tada
does is give you a fully typed GraphQL document that
tells TypeScript what the Result
and Variables
types are just via inference.
Client Support
Today, supporting typed documents in GraphQL is an accepted and de-facto standard,
and below you can find a non-exhaustive list of GraphQL clients that support
typed documents and will hence also work well with gql.tada
.