Composite Schemas Spec
The GraphQL Composite Schemas Spec is being actively worked upon on GitHub. Most importantly for readers are:
- Source Schema directives defining the directives that may be used by subgraphs such as
@lookup
. - FieldSelectionMap specification used in
@is
and@require
.
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.