Working with Entities

Use Apollo Connectors to enrich and reference entities using REST APIs


Apollo Connectors are designed to simplify working with entities in federated graphs. An entity is any object that can identified with one or more unique key fields, much like rows in a database table. The different data fields are like columns in the table. GraphQL federation uses entity types to represent object types that combine information from multiple data sources.

This guide shows some common patterns for working with entities via Connectors. Before diving into example Connectors, it's helpful to understand a little about how to identify entities.

Identifying entities

Most APIs associate entities with particular business domains. When working with REST APIs, if there's a GET endpoint with a unique identifier (for example, /products/:id) it's likely you're dealing with an entity.

Combining endpoints to complete an entity

Connectors can orchestrate multiple endpoints to provide a unified representation of a type. For example, the Connectors quickstart shows how to create a Connector that combines product data from a /products endpoint that provides a few fields for all products and a /products/:id endpoint that provides more fields, specifically product variant information, for a single product.

GraphQL
1type Query {
2  products: [Product]
3    @connect(
4      source: "ecomm"
5      http: { GET: "/products" }
6      selection: """
7      $. products {
8        id
9        name
10        description
11      }
12      """
13    )
14
15  product(id: ID!): Product
16    @connect(
17      source: "ecomm"
18      http: { GET: "/products/{$args.id}" }
19      selection: """
20      id
21      name
22      description
23      variants {
24        id: variantID
25        name
26      }
27      """
28      entity: true
29  )
30}
31
32type Product {
33  id: ID!
34  name: String
35  description: String
36  variants: [Variant]
37}
38
39type Variant {
40  id: ID!
41  name: String
42}

One important detail is marking the /products/:id Connector with entity: true on line 28. This indicates that this endpoint can fetch additional fields for individual product entities. These entities are fetched using a unique key (the id field the /products endpoint returns, in this case).

How does the router handle this query?
When the GraphOS Router receives a query for fields from both endpoints, it generates a query plan that sequences calls across the two endpoints. For example:
GraphQL
query ListProductsAndVariants {
  products {
    id
    name
    variants {
      id
      name
    }
  }
}

Combining representations of the same type with multiple Connectors

You can add multiple Connectors to the same field, and the GraphOS Router chooses to call one or both depending on the fields in the client query. This is especially useful when you have multiple API versions.

GraphQL
type Query {
  product(id: ID!): Product
    @connect(
      source: "ecomm"
      http: { GET: "/v1/products/{$args.id}" }
      selection: """
      id
      color
      }
      """
      entity: true
    )
    @connect(
      source: "ecomm"
      http: { GET: "/v2/products/{$args.id}" }
      selection: """  
      id
      name
      price
      variants {
        id: variantID
        color
      }
      """
      entity: true
    )
}

type Product {
  id: ID!
  name: String
  price: Int
  color: String @deprecated(reason: "Use the 'variants' field instead")
  variants: [Variant]
}

type Variant {
  id: ID!
  color: String
}

Efficiently fetching additional information

If you provide the GraphOS Router with multiple Connectors that fetch additional information, it can choose the optimal endpoint to resolve the requested fields. For example, if the client operation requests the reviews field, the GraphOS Router chooses the second Connector to fetch the reviews along with the product details.

GraphQL
type Query {
  product(id: ID!): Product
    @connect(
      source: "ecomm"
      http: { GET: "/products/{$args.id}" }
      selection: """
      id
      name
      price
      """
      entity: true
    )
    @connect(
      source: "ecomm"
      http: { GET: "/products/{$args.id}?include=reviews" }
      selection: """
      id
      name
      price
      reviews {
        id
        rating
        comment
      }
      """
      entity: true
    )
}

type Product {
  id: ID!
  name: String
  price: Int
  reviews: [Review]
}

type Review {
  id: ID!
  rating: Float
  comment: String
}

Working across subgraphs

Connectors work seamlessly to combine data across multiple services, or 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.

GraphQL
products.graphql
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.

GraphQL
products.graphql
type Query {
  products: [Product]
    @connect(
      source: "ecomm"
      http: { GET: "/products" }
      selection: """
      $. products {
        id
        name
      }
      """
    )

  product(id: ID!): Product
  @connect(
    source: "ecomm"
    http: { GET: "/products/{$args.id}" }
    selection: """
    id
    description
    """
    entity: true
  )
}

type Product {
  id: ID!
  title: String
  description: String
}
note
See Combining endpoints to complete an entity to learn more about how this works.

Product review data may come from a separate review subgraph that connects a /reviews?product_id=:productId endpoint to your graph.

GraphQL
reviews.graphql
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.

Reusing entity fields 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 duplicate Connector logic to do that:

GraphQL
❌ Unnecessary duplication
1type Query {
2  product(id: ID!): Product
3    @connect(
4      source: "ecomm"
5      http: { GET: "/products/${args.id}" }
6      selection: """
7      id
8      name
9      description
10      """
11      entity: true
12    )
13  
14  review(id: ID!): Review
15    @connect(
16      source: "ecomm"
17      http: { GET: "/reviews/${args.id}" }
18      selection: """
19      id
20      rating
21      comment
22      product: $.product {
23        id: productId
24        name
25        description
26      }
27      """
28    )
29}
30
31type Product {
32  id: ID!
33  name: String
34  description: String
35}
36
37type Review {
38  id: ID!
39  rating: Float!
40  comment: String
41  product: Product!
42}

Since Product is defined as an entity (see the entity: true on line 11), you can avoid the duplicate Connector entirely:

GraphQL
✅ Efficient entity usage
1type Query {
2  product(id: ID!): Product
3    @connect(
4      source: "ecomm"
5      http: { GET: "/products/${args.id}" }
6      selection: """
7      id
8      name
9      description
10      """
11      entity: true
12    )
13  
14  review(id: ID!): Review
15    @connect(
16    source: "ecomm"
17    http: { GET: "/reviews/${args.id}" }
18    selection: """
19    id
20    name
21    comment
22    product: { id: productId }
23    """
24  )
25}
26
27type Product {
28  id: ID!
29  name: String
30  description: String
31}
32
33type Review {
34  id: ID!
35  rating: Float!
36  comment: String
37  product: Product!
38}

Rather than fetching a product with a selection mapping duplicated from the Query.product Connector, you can use product: { id: productId }.

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 uses the existing Query.product Connector to fetch the full product object. There's no need to repeat the endpoint or selection logic. Connectors handle that for you.

tip
Key takeaway When working with entities across your schema, you'll often encounter fields like 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:
GraphQL
product {
  id: productId
}
This tells the router to resolve product as an entity, using the existing connector logic defined elsewhere in your schema. It makes your schema cleaner, avoids duplication, and lets you reuse the entity's fields wherever they're needed.

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:

GraphQL
users.graphql
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.

GraphQL
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 that it is included 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

Feedback

Forums