Composite Schemas Spec

The GraphQL Composite Schemas Spec is being actively worked upon on GitHub. Most importantly for readers are:

The Grafbase Gateway partially implements the specification with the goal of implementing it fully over time. In addition, we also extend the specification to better fit the use case of non-GraphQL data sources.

The current document describe both the current implemented parts of the specification and any additions we made.

Directives

Directives can be imported with @link similarly to Apollo Federation's directives:

extend schema @link( url: "https://specs.grafbase.com/composite-schemas/v1" import: ["@lookup", "@key"] )

In all examples within this document, directives will be imported with this @link directive if not specified explicitly.

It is not possible to use both Apollo Federation and Composite Schemas together in the same subgraphs. But a super graph can be composed of subgraphs used either.

  • @internal
  • @require (coming soon)

@external, @inaccessible, @shareable, @provides and @override are implemented in the same way for both Apollo Federation and Composite Schemas.

@key in Composite Schemas is treated as a non-resolvable Apollo Federation @key. So @key(fields: "id") is implicitly @key(fields: "id", resolvable: false). Resolvable in that context means that the subgraph can provide the entity through Apollo Federation's custom _entities field. So @key in Composite Schemas only defines the key fields, without specifying how the entities can be retrieved. A subgraph only defining the following does not provide any means for the supergraph to retrieve a Post entity. _entities and _service are not assumed to exist. See @lookup directive to provide entities.

extend schema @link( url: "https://specs.grafbase.com/composite-schemas/v1" import: ["@key"] ) type Post @key(fields: "id") { id: ID! }
directive @lookup on FIELD_DEFINITION

@lookup defines how entities can be accessed by the supergraph. In the following example @lookup would automatically be used to a Post entity.

type Query { post(id: ID!): Post! @lookup } type Post @key(fields: "id") { id: ID! }

Today the Grafbase Gateway only supports the batch variant, which doesn't exist in the Composite Schemas spec, for extensions like Postgres:

type Query { posts(ids: [ID!]): [Post!] @lookup } type Post @key(fields: "id") { id: ID! }

@lookup will automatically detect the right argument to inject for single and composite keys as long as there isn't any ambiguity. Only one @lookup can exists in a subgraph for any given @key.

Support for single lookup for any resolver extensions/GraphQL subgraphs and explicit mapping with @is is coming soon.

directive @derive on FIELD_DEFINITION

The @derive directive creates a virtual entity field when the original data only exposes ids. It doesn't exist in the Composite Schemas Spec, we added it to simplify the integration of non-Graphql data sources such as REST or gRPC.

type Query { posts: [Post!]! } type Post { authorId: ID! # This field is not provided by the subgraph author: User! @derive } type User @key(fields: "id") { id: ID! }

The author field is resolved by the supergraph, derived from the authorId field. This simple adjustment makes it possible to query fields from other subgraphs:

{ posts { author { id name } } }

@derive will, similarly to @lookup, automatically detect the relevant fields based on their name and type given the defined @key. A derived field must match at least one @key. It may provide more fields or multiple keys, and you can also hide the real subgraphs fields from your final API schema with @inaccessible:

type Post { authorId: ID! @inaccessible # This field is not provided by the subgraph author: User! @derive }

@derive supports multiple use cases and can be explicitly specified with the help of the @is directive:

  • Single key:
type Post { authorId: ID! author: User! @derive # or author: User! @derive @is(field: "{ id: authorId }") } type User @key(fields: "id") { id: ID! }
  • Composite key:
type Post { authorTenantId: ID! authorEmail: String! author: User! @derive # or author: User! @derive @is(field: "{ tenantId: authorTenantId email: authorEmail }") } type User @key(fields: "tenantId email") { tenantId: ID! email: String! }
  • Single key list
type Post { commentIds: [ID!] comments: [Comment!]! @derive # or comments: [Comment!]! @derive @is(field: "commentIds[{ id: . }]") } type Comment @key(fields: "id") { id: ID! }
  • Composite key list
type Post { reviewersTenantIdAndEmail: [TenantIdAndEmail!]! reviewers: [User!]! @derive # or reviewers: [User!]! @derive @is(field: "reviewersTenantIdAndEmail[{ tenantId email }]") } type TenantIdAndEmail { tenantId: ID! email: String! } type User @key(fields: "tenantId email") { tenantId: ID! email: String! }

The only limit to @derive today is that nested fields are not supported.

See @lookup and @derive for more information.