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
1