Working with Entities Across Subgraphs
Learn how to use entities across services with Apollo Connectors
Entities in GraphQL Federation allow you to combine data from across multiple services called subgraphs. This guide shows how to use Apollo Connectors with entities in a federated architecture.
Overview
Connectors work seamlessly to combine data across multiple subgraphs into a unified GraphQL API, or supergraph. You can use Connectors to contribute to and reference entities from other subgraphs, whether those are different REST or GraphQL APIs.
Working across subgraphs with keys
As your schema grows, it's common to maintain each subgraph schema in its own schema file, for example, products.graphql
, reviews.graphql
, etc. Modularizing schemas likes this improves collaboration by allowing independent development and deployment across teams.
To identify an entity across different subgraphs, GraphQL schemas rely on the @key
directive. The @key
directive defines which key field(s) to cross-identify a particular entity type.
For example, a Product
type may be uniquely identifiable by a SKU or ID.
type Product @key(fields: "id") {
id: ID!
name: String!
price: Int
}
Contributing entity fields across Connectors
Any number of different subgraphs can contribute fields to an entity. For example, you may have both a products subgraph and a reviews subgraph contributing fields to a Product entity.
The products subgraph may provide product data via a Connector that orchestrates /products/
and /products/:id
endpoints.
type Query {
products: [Product]
@connect(
source: "ecomm"
http: { GET: "/products" }
selection: """
$. products {
id
name
}
"""
)
}
type Product
@connect(
source: "ecomm"
http: { GET: "/products/{$this.id}" }
selection: """
id
description
"""
)
{
id: ID!
title: String
description: String
}
Product review data may come from a separate review subgraph that connects a /reviews?product_id=:productId
endpoint to your graph.
type Product @key(fields: "id") {
id: ID!
reviews: [Review]
@connect(
source: "example"
http: { GET: "/reviews?product_id={$this.id}" }
selection: """
$.reviews {
id
title
body
rating
}
"""
)
}
type Review {
id: ID!
title: String
body: String
rating: Int
}
The @connect
directive defines the reviews
fields to request from the /reviews
endpoint.
The id
used as a parameter in the endpoint URL comes from the product's id
field, which comes from the products subgraph.
Using entities across Connectors
Imagine all your reviews include a productId
field to reference the product they're about. When displaying a review in detail, you might need to fetch some of the related product's fields.
It can be tempting to structure your Connectors like this:
1type Query {
2 review(id: ID!): Review
3 @connect(
4 source: "ecomm"
5 http: { GET: "/reviews/{$args.id}" }
6 selection: """
7 id
8 rating
9 comment
10 productId
11 """
12 )
13}
14
15type Product {
16 id: ID!
17 name: String
18 description: String
19}
20
21type Review {
22 id: ID!
23 rating: Float!
24 comment: String
25 productId: ID!
26 product: Product!
27 @connect(
28 source: "ecomm"
29 http: { GET: "/products/{$this.productId}" }
30 selection: """
31 id
32 name
33 description
34 """
35 )
36}
Instead of carrying the productId
field around ourselves, you can model the Review.product
relationship using entity types directly. Then you can use a Connector on Product
to fetch the product details.
1type Query {
2 review(id: ID!): Review
3 @connect(
4 source: "ecomm"
5 http: { GET: "/reviews/{$args.id}" }
6 selection: """
7 id
8 name
9 comment
10 product: { id: productId }
11 """
12 )
13}
14
15type Product
16 @connect(
17 source: "ecomm"
18 http: { GET: "/products/{this.id}" }
19 selection: """
20 id
21 name
22 description
23 """
24 )
25{
26 id: ID!
27 name: String
28 description: String
29}
30
31type Review {
32 id: ID!
33 rating: Float!
34 comment: String
35 product: Product!
36}
In this context, product: { id: productId }
tells Connectors: "This review references a product that you can fetch with this ID."
Because Product
is an entity, Connectors automatically use the existing Product
Connector to fetch the full product object.
entityId
or entity_id
in API responses. These are opportunities to use entity references instead of duplicating Connector mapping selections. Rather than exposing the raw ID directly, you can structure it as a nested object:product: {
id: productId
}
Referencing entities across Connectors
If your REST API provides foreign key references, you can use them to reference entities and fetch corresponding fields from a different subgraph.
For example, if your REST API lets you fetch a user's favorite products and provides those products' IDs, you can use the @connect
directive to reference the Product
entity:
type User @key(fields: "id") {
id: ID!
favoriteProducts: [Product]
@connect(
source: "example"
http: { GET: "/users/{$this.id}/favorites" }
selection: """
$.products {
id: productId
}
"""
)
}
type Product @key(fields: "id", resolvable: false) {
id: ID!
}
The mapping language maps the productId
to the Product
entity's id
field.
The resolvable: false
argument denotes that while this subgraph includes a definition for the Product
entity, it doesn't provide a way to fetch or resolve all of it's fields. Learn more.
Adding a computed field using @requires
You might need to compute a field's value based on data from other subgraphs that contribute to an entity type.
The @requires
directive allows a subgraph to declare that it depends on specific fields from the same entity before resolving a field. The @external
directive declares that a field is resolved from a different subgraph.
1type Product @key(fields: "id") {
2 id: ID!
3 weight: Int @external
4 shippingCost(zipCode: String): Int
5 @requires(fields: "weight")
6 @connect(
7 source: "example"
8 http: {
9 GET: "/shipping?zip={$args.zipCode}&weight={$this.weight}"
10 }
11 selection: "$.result"
12 )
13}
In this example, shippingCost
depends on the weight
field.
Because weight
is marked as @external
(coming from another subgraph), the @requires
directive ensures its inclusion in the query plan before resolving shippingCost
.
Compatibility with other federation features
Connectors work seamlessly with many other federation features. You can use directives like @tag
, @inaccessible
, @provides
, and more alongside @connect
.
See the limitations reference for a list of unsupported federation directives.
Additional resources
See the entity documentation for more information on using entities in GraphQL Federation.
Check out the Thinking in Entities blog post for best practices for using entities with Connectors.