Entities in Apollo Federation

Reference and extend types across subgraphs


In Apollo Federation, an entity is an object type that you define canonically in one subgraph and can then reference and extend in other subgraphs.

Entities are the core building block of a federated graph.

Defining entities

In a GraphQL schema, you can designate any object type as an entity by adding a @key directive to its definition, like so:

GraphQL
products
1type Product @key(fields: "upc") {
2  upc: String!
3  name: String!
4  price: Int
5}

Types besides object types (such as unions and interfaces) cannot be entities.

The @key directive defines the entity's primary key, which consists of one or more of the type's fields. Like primary keys in other systems, an entity's primary key must uniquely identify a particular instance of that entity.

In the example above, the Product entity's primary key is its upc field. The gateway uses an entity's primary key to match data from different subgraphs to the same object instance.

An entity's @key cannot include fields that return a union or interface.

Multiple primary keys

You can define more than one primary key for an entity, when applicable.

In the following example, a Product entity can be uniquely identified by either its upc or its sku:

GraphQL
products
1type Product @key(fields: "upc") @key(fields: "sku") {
2  upc: String!
3  sku: String!
4  price: String
5}

This pattern is helpful when different subgraphs interact with different fields of an entity. For example, a reviews subgraph might refer to products by their UPC, whereas an inventory subgraph might use SKUs.

Compound primary keys

A single primary key can consist of multiple fields, and even nested fields.

The following example shows a primary key that consists of both a user's id and the id of that user's associated organization:

GraphQL
directory
1type User @key(fields: "id organization { id }") {
2  id: ID!
3  organization: Organization!
4}
5
6type Organization {
7  id: ID!
8}

Referencing entities

After you define an entity in one subgraph, other subgraphs can then reference that entity in their schema.

For example, let's say we have a products subgraph that defines the following Product entity:

GraphQL
products
1type Product @key(fields: "upc") {
2  upc: String!
3  name: String!
4  price: Int
5}

A reviews subgraph can then add a field of type Product to its Review type, like so:

GraphQL
reviews
1type Review {
2  score: Int!
3  product: Product!
4}
5
6# This is a required "stub" of the Product entity (see below)
7extend type Product @key(fields: "upc") {
8  upc: String! @external
9}

To reference an entity that originates in another subgraph, the reviews subgraph needs to define a stub of that entity to make its own schema valid. The stub includes just enough information for the subgraph to know how to uniquely identify a particular Product:

  • The extend keyword indicates that Product is an entity that's defined in another subgraph.

  • The @key directive indicates that Product uses the upc field as its primary key. This value must match the value of exactly one @key defined in the entity's originating subgraph (even if the entity defines multiple primary keys).

  • The upc field must be present because it's part of the specified @key. It also requires the @external directive to indicate that it originates in another subgraph.

This explicit syntax has several benefits:

  • It's standard GraphQL grammar.

  • It enables you to run the reviews subgraph standalone with a valid schema, including a Product type with a single upc field.

  • It provides strong typing information that lets you catch mistakes at schema composition time.

Resolving entities

Let's say our reviews subgraph from Referencing entities defines the following Query type:

GraphQL
reviews
1type Query {
2  latestReviews: [Review!]
3}

That means the following query is valid against our federated graph:

GraphQL
1query GetReviewsAndProducts {
2  latestReviews {
3    score
4    product {
5      upc
6      price # Not defined in reviews!
7    }
8  }
9}

Now we have a problem: this query starts its execution in the reviews subgraph (where latestReviews is defined), but that subgraph doesn't know that Product entities have a price field! Remember, the reviews subgraph only knows about its stub fields of Product.

Because of this, the gateway needs to fetch price from the products subgraph instead. But how does the gateway know which products it needs to fetch the prices for?

To solve this, we add a resolver to each subgraph:

Entity representations

In our example, the reviews subgraph needs to define a resolver for its stub version of the Product entity. The reviews subgraph doesn't know much about Products, but fortunately, it doesn't need to. All it needs to do is return data for the fields it does know about, like so:

JavaScript
resolvers.js
1// Reviews subgraph
2const resolvers = {
3  Review: {
4    product(review) {
5      return {
6        __typename: "Product",
7         upc: review.upc
8      };
9    }
10  },
11  // ...
12}

This resolver's return value is a representation of a Product entity (because it represents an entity from another subgraph). A representation always consists of:

  • A __typename field

  • Values for the entity's primary key fields (upc in this example)

Because an entity can be uniquely identified by its primary key fields, this is all the information the gateway needs to fetch additional fields for a Product object.

Reference resolvers

As a reminder, here's the example query we're executing across our subgraphs:

GraphQL
1query GetReviewsAndProducts {
2  latestReviews {
3    score
4    product {
5      upc
6      price # Not defined in reviews!
7    }
8  }
9}

The gateway knows it can't fetch Product.price from the reviews subgraph, so first it executes the following query on reviews:

GraphQL
1query {
2  latestReviews {
3    score
4    product { # List of Product representations
5      __typename
6      upc
7    }
8  }
9}

Notice that this query omits price but adds __typename, even though it wasn't in the original query string! This is because the gateway knows it needs all of the fields in each Product's representation, including __typename.

With these representations available, the gateway can now execute a second query on the products subgraph to fetch each product's price. To support this special query, the products subgraph needs to define a reference resolver for the Product entity:

JavaScript
resolvers.js
1// Products subgraph
2const resolvers = {
3  Product: {
4    __resolveReference(reference) {
5      return fetchProductByUPC(reference.upc);
6    }
7  },
8  // ...
9}

In the example above, fetchProductByUPC is a hypothetical function that fetches a Product's full details from a data store based on its upc.

A reference resolver (always called __resolveReference) provides the gateway direct access to a particular entity's fields, without needing to use a custom query to reach that entity. To use a reference resolver, the gateway must provide a valid entity representation, which is why we created the resolver in the reviews subgraph first!

To learn more about __resolveReference, see the API docs.

After fetching the price field from products via a reference resolver, the gateway can intelligently merge the data it obtained from its two queries into a single result and return that result to the querying client.

Extending entities

A subgraph can add fields to an entity that's defined in another subgraph. This is called extending the entity.

When a subgraph extends an entity, the entity's originating subgraph is not aware of the added fields. Only the extending subgraph (along with the gateway) knows about these fields.

Each field of an entity should be defined in exactly one subgraph. Otherwise, a schema composition error will occur.

Example #1

Let's say we want to add a reviews field to the Product entity. This field will return a list of reviews for the product. The Product entity originates in the products subgraph, but it makes more sense for the reviews subgraph to resolve this particular field.

To handle this case, we can extend the Product entity in the reviews subgraph, like so:

GraphQL
reviews
1extend type Product @key(fields: "upc") {
2  upc: String! @external
3  reviews: [Review]
4}

This definition is nearly identical to the stub we defined for the Product type in Referencing entities. All we've added is the reviews field. We don't include an @external directive, because this field does originate in the reviews subgraph.

Whenever a subgraph extends an entity with a new field, it's also responsible for resolving that field. The gateway is automatically aware of this responsibility. In our example:

  1. The gateway first fetches the upc field for each Product from the products subgraph.

  2. The gateway then passes those upc values to the