Develop your graph with Connectors

Use your graph to orchestrate REST API calls with Apollo Connectors and Apollo Federation


In the previous guide, you set up a graph in GraphOS Studio and started a local dev session. In this guide, you'll:

  • Walk through an example GraphQL schema for an Ecommerce app to learn how Apollo Connectors can integrate REST APIs with declarative code.

  • Make requests in Apollo Sandbox to observe how Connectors orchestrate calls to REST APIs.

  • Create more Connectors to bring additional features to your graph.

Let's start developing!

Love developer tooling? 🛠️
Whether you're developing in VS Code, Vim/NeoVim, or with a JetBrains IDE, Apollo's got you covered with tooling for a better developer experience. Now is a great time to set up these plugins to get better autocomplete and faster feedback as you develop.

Explore example Connector

Let's start by taking a look at the products.graphql GraphQL schema you cloned to see how Connectors work.

  1. The two uses of the @link directive enable the latest versions of federation and Connectors.

    GraphQL
    products.graphql
    extend schema
      @link(
        url: "https://specs.apollo.dev/federation/v2.10"
      )
      @link(
        url: "https://specs.apollo.dev/connect/v0.1"
        import: ["@connect", "@source"]
      )
  2. In the next few lines, the @source directive creates a reusable configuration that each Connector can reference. In this case, it only sets the baseURL to the ecommerce demo API this guide uses.

    GraphQL
    products.graphql
      @source(
          name: "ecomm"
          http: {
              baseURL: "https://ecommerce.demo-api.apollo.dev/"
              headers: [
                  # If your API requires headers, add them here and in your router.yaml file.
                  { name: "name", value: "{$config.apiKey}" }
              ]
              }
      )
  3. Product is the standard object type this graph returns. When building out your own schema, you replace it with object types in your APIs.

    GraphQL
    products.graphql
    type Product {
      id: ID!
      name: String
      description: String
    }
  4. The Query type has a products field that the @connect directive implements.

    What's the Query type?
    In a GraphQL schema, the Query type defines the entry points for reading data from a GraphQL schema. It acts as the root type that clients use to request specific fields and traverse the graph.
    GraphQL
    products.graphql
    type Query {
      products: [Product]
        # A @connect directive defines the API data source of a GraphQL schema field.
        @connect(
          source: "ecomm"
          http: { GET: "/products" }
          selection: """
          $.products {  
            id
            name
            description
          }
          """
        )
    }

    Here is what each of @connect's arguments does:

    • source: "ecomm" refers to the @source instance.

    • http sets the method (GET) and path (/products) for the request.

    • selection maps the REST API's JSON response to the fields in the GraphQL schema.

To understand how to map a JSON response to your GraphQL schema, you inspect the JSON response. Here's what the response for /products looks like:

/products
JSON
1{
2  "products": [
3    {
4      "id": 1,
5      "name": "Lunar Rover Wheels",
6      "createdAt": 1636742972000,
7      "updatedAt": 1636742972000,
8      "description": "Designed for traversing the rugged terrain of the moon, these wheels provide unrivaled traction and durability. Made from a lightweight composite, they ensure your rover is agile in challenging conditions.",
9      "slug": "lunar-rover-wheels",
10      "tags": [
11        {
12          "tagId": "space",
13          "name": "Space"
14        },
15        {
16          "tagId": "engineering",
17          "name": "Engineering"
18        },
19        {
20          "tagId": "rover",
21          "name": "Rover"
22        }
23      ],
24      "category": "Engineering Components",
25      "availability": "AVAILABLE"
26    },
27    {
28      "id": 2,
29      "name": "Zero-Gravity Moon Boots",
30      "createdAt": 1636742972000,
31      "updatedAt": 1636742972000,
32      "description": "Experience weightlessness with our Zero-Gravity Moon Boots! Specifically designed to provide comfort and support for lunar explorers, these boots are perfect for hopping around on the moon's surface.",
33      "slug": "zero-gravity-moon-boots",
34      "tags": [
35        {
36          "tagId": "space",
37          "name": "Space"
38        },
39        {
40          "tagId": "apparel",
41          "name": "Apparel"
42        },
43        {
44          "tagId": "moon",
45          "name": "Moon"
46        }
47      ],
48      "category": "Apparel",
49      "availability": "AVAILABLE"
50    },
51    {
52      "id": 3,
53      "name": "Asteroid Blaster Tool",
54      "createdAt": 1636742972000,
55      "updatedAt": 1636742972000,
56      "description": "A must-have tool for all space engineers! This high-powered blaster is designed for asteroid excavation and resource gathering, featuring an adjustable power setting for all your blasting needs.",
57      "slug": "asteroid-blaster-tool",
58      "tags": [
59        {
60          "tagId": "engineering",
61          "name": "Engineering"
62        },
63        {
64          "tagId": "tools",
65          "name": "Tools"
66        },
67        {
68          "tagId": "space",
69          "name": "Space"
70        }
71      ],
72      "category": "Engineering Tools",
73      "availability": "AVAILABLE"
74    },
75    {
76      "id": 4,
77      "name": "Interstellar Communication Device",
78      "createdAt": 1636742972000,
79      "updatedAt": 1636742972000,
80      "description": "Stay connected across the galaxies! Our Interstellar Communication Device allows you to send messages to fellow space travelers with secure encryption and planetary signal enhancement.",
81      "slug": "interstellar-communication-device",
82      "tags": [
83        {
84          "tagId": "tech",
85          "name": "Tech"
86        },
87        {
88          "tagId": "space",
89          "name": "Space"
90        },
91        {
92          "tagId": "communication",
93          "name": "Communication"
94        }
95      ],
96      "category": "Communication Devices",
97      "availability": "AVAILABLE"
98    },
99    {
100      "id": 5,
101      "name": "Galactic Navigation System",
102      "createdAt": 1636742972000,
103      "updatedAt": 1636742972000,
104      "description": "Never get lost in space again! Our Galactic Navigation System provides real-time location tracking, route planning, and hazard warnings to ensure safe travels through the cosmos.",
105      "slug": "galactic-navigation-system",
106      "tags": [
107        {
108          "tagId": "tech",
109          "name": "Tech"
110        },
111        {
112          "tagId": "navigation",
113          "name": "Navigation"
114        },
115        {
116          "tagId": "space",
117          "name": "Space"
118        }
119      ],
120      "category": "Navigation Devices",
121      "availability": "AVAILABLE"
122    },
123    {
124      "id": 6,
125      "name": "Mars Terrain Analyzer",
126      "createdAt": 1636742972000,
127      "updatedAt": 1636742972000,
128      "description": "A state-of-the-art device crafted for analyzing the soil and terrain of Mars. It provides scientists with crucial data about soil composition, moisture levels, and potential resources for future colonization.",
129      "slug": "mars-terrain-analyzer",
130      "tags": [
131        {
132          "tagId": "science",
133          "name": "Science"
134        },
135        {
136          "tagId": "analysis",
137          "name": "Analysis"
138        },
139        {
140          "tagId": "mars",
141          "name": "Mars"
142        }
143      ],
144      "category": "Scientific Devices",
145      "availability": "AVAILABLE"
146    },
147    {
148      "id": 7,
149      "name": "Comet Dust Collector",
150      "createdAt": 1636742972000,
151      "updatedAt": 1636742972000,
152      "description": "Gather dust from comets with this innovative collector! Designed for deep-space research, it captures samples with high efficiency while ensuring the integrity of the materials.",
153      "slug": "comet-dust-collector",
154      "tags": [
155        {
156          "tagId": "science",
157          "name": "Science"
158        },
159        {
160          "tagId": "research",
161          "name": "Research"
162        },
163        {
164          "tagId": "space",
165          "name": "Space"
166        }
167      ],
168      "category": "Research Equipment",
169      "availability": "AVAILABLE"
170    },
171    {
172      "id": 8,
173      "name": "Planetary Habitat Module",
174      "createdAt": 1636742972000,
175      "updatedAt": 1636742972000,
176      "description": "An essential for any space colonization mission! This habitat module provides a reliable, comfortable living environment for astronauts on planetary missions, complete with life support systems and smart technology.",
177      "slug": "planetary-habitat-module",
178      "tags": [
179        {
180          "tagId": "construction",
181          "name": "Construction"
182        },
183        {
184          "tagId": "space",
185          "name": "Space"
186        },
187        {
188          "tagId": "habitat",
189          "name": "Habitat"
190        }
191      ],
192      "category": "Living Spaces",
193      "availability": "AVAILABLE"
194    },
195    {
196      "id": 9,
197      "name": "Satellite Launch Pad Kit",
198      "createdAt": 1636742972000,
199      "updatedAt": 1636742972000,
200      "description": "Get your small satellites off the ground with our Satellite Launch Pad Kit! This kit includes everything you need to successfully launch mini satellites into orbit, including launch software and tracking system.",
201      "slug": "satellite-launch-pad-kit",
202      "tags": [
203        {
204          "tagId": "space",
205          "name": "Space"
206        },
207        {
208          "tagId": "launch",
209          "name": "Launch"
210        },
211        {
212          "tagId": "kit",
213          "name": "Kit"
214        }
215      ],
216      "category": "Launch Equipment",
217      "availability": "AVAILABLE"
218    },
219    {
220      "id": 10,
221      "name": "Planetary Resource Extractor",
222      "createdAt": 1636742972000,
223      "updatedAt": 1636842972000,
224      "description": "Tap into the resources of asteroids and moons! The Planetary Resource Extractor is engineered for durability and efficiency to help miners gather valuable materials from celestial bodies.",
225      "slug": "planetary-resource-extractor",
226      "tags": [
227        {
228          "tagId": "engineering",
229          "name": "Engineering"
230        },
231        {
232          "tagId": "mining",
233          "name": "Mining"
234        },
235        {
236          "tagId": "space",
237          "name": "Space"
238        }
239      ],
240      "category": "Mining Equipment",
241      "availability": "AVAILABLE"
242    }
243  ],
244  "summary": {
245    "total": 30
246  }
247}

Notice that the /products endpoint returns many more fields than id, name, and description. With Connectors (and for the sake of simplicity in this guide), you can select and return only the ones you need via selection mapping.

Selection mapping

Selection mapping is how you "connect" the data from an API response to your graph. You use selection mapping syntax in your GraphQL schema to select and map the pieces of an API response to fields in your GraphQL schema.

You write your selection mapping in the selection field:

GraphQL
selection: """
$.products {  
  id
  name
  description
}

In selection mapping, $ refers to the root of the response body. Since the response body for /products returns a list of products nested under a products key, the first part of the selection in the example schema is $.products. The product fields to map are wrapped within the $.products {}object.

Since the Product type fields declared in the GraphQL schema map cleanly to product fields in the JSON response, the selection uses shorthand for field names.

These two selection mappings are equivalent:

GraphQL
selection: """
$.products {  
  id
  name
  description
}
GraphQL
selection: """
$.products {  
  id: id
  name: name
  description: description
}
What if schema field names are different from the JSON response?
You can use selection mapping to rename fields in your API. For example, if your REST API returns a name field that you want to rename title in your GraphQL API, you can map it like so:
GraphQL
selection: """
$.products {  
  id,
  title: name
  description
}

Notice you can still use shorthand for any other field names that don't need to change.

That wraps up our overview of products.graphql! Let's test things out by running a request in the Apollo Sandbox.

Run a request

Running rover dev starts an instance of Apollo Sandbox, a local GraphQL server where you can:

  • Write GraphQL operations and make requests to your GraphQL API.

  • Validate the response and debug issues.

To run a request in Sandbox:

  1. In a browser, go to http://localhost:4000.

  2. In the central Operation panel, copy and paste the following query to get all products.

    GraphQL
    Example query
    query Products {
      products {
        id
        name
        description
      }
    }
  3. Run the request by clicking the ▶️ Products button. Check the response.

    You should see the products data you selected from the /products endpoint.

    Viewing requests in the Connectors Debuggers in Apollo Sandbox

That's it—a fully functional GraphQL API built on Connectors! Next, you'll create a second Connector from scratch. Before that, this guide offers a slight detour to showcase the Sandbox's Connectors Debugger. If you're eager to write your own Connector, skip to the next section and return to this debugging section when you need it.

Explore Connectors debugger

Explore the Connectors debugger

Suppose the example Connector hadn't worked out of the box—for example, that the selection mapping was incorrect. You can use the Connectors Debugger in the Sandbox to figure out what's gone wrong.
  1. Update your products.graphql so that the selection mapping is incorrect in the following way:
    GraphQL
    products.graphql
    type Query {
      products: [Product]
        # A @connect directive defines the API data source of a GraphQL schema field.
        @connect(
          source: "ecomm"
          http: { GET: "/products" }
          selection: """
          id
          name
          description
          """
        )
    }
    Why is this incorrect?
    This selection mapping doesn't wrap the field names in $.products{ } and therefore doesn't match the JSON response from /products. It would match if that endpoint returned a top-level list of products that weren't nested under a products key.
  2. Save your products.graphql to hot reload the Sandbox.
  3. Rerun the Products query by clicking the ▶️ Products button. Check the response.
    GraphQL
    Example query
    query Products {
      products {
        id
        name
        description
      }
    }
    With the incorrect schema, the Response panel shows null for the data returned.Query products' info with Connector subgraphThe Response panel also has a Connectors Debugger that can help determine what's gone wrong.
  4. Open it by clicking Response and selecting Connectors Debugger from the dropdown.Opening the Connectors Debugger from Apollo Sandbox
  5. You can see three mapping errors occurred, even though the HTTP response status code was 200.Viewing requests in the Connectors Debuggers in Apollo Sandbox
  6. Clicking into the Mapping panel displays some helpful error messages: Property .description not found in object, etc.
  7. The Response body panel of the debugger shows the raw JSON response and where the schema went wrong. The /products endpoint doesn't just return a list, it returns a list of products nested under the products key.Viewing requests in the Connectors Debuggers in Apollo Sandbox
  8. Correct the selection mapping in products.graphql by reverting to the original schema, nesting the current selection inside of $.products{ }.
    GraphQL
    products.graphql
    type Query {
      products: [Product]
        # A @connect directive defines the API data source of a GraphQL schema field.
        @connect(
          source: "ecomm"
          http: { GET: "/products" }
          selection: """
          $.products {
            id
            name
            description
          }
          """
        )
    }
  9. Save your changes to the products.graphql file in your editor.
  10. Rerun your request by clicking the ▶️ Products button. The Connectors Debugger should now show no errors. Toggle the right panel to the Response to confirm the response is as expected.Viewing requests in the Connectors Debuggers in Apollo Sandbox

Create a Connector with arguments

Next, you'll create your own Connector to interact with the /products/:id REST endpoint. Adding this Connector to your schema lets clients request a single product by ID using the same GraphQL API that lists all products.

  1. Add a new product field to the root Query type that requires an argument called id and returns a Product.

    GraphQL
    products.graphql
    type Query {
      products: [Product]
        @connect(
          source: "ecomm"
          http: { GET: "/products" }
          selection: """
          $.products {
            id
            name
            description
          }
          """
        )
    
      product(id: ID!): Product
    }
    GraphQL argument syntax
    In a GraphQL schema, arguments are enclosed in parentheses that come after a field's name. For example, (id: ID!) is an argument in the example above. Each argument consists of two parts: 1) the argument's name and 2) the type of data that will be passed as the argument's value. Learn more about GraphQL arguments.
  2. Next, use the @connect directive to connect Query.product to the /products/:id endpoint. You can use arguments as path and query parameters via the $args variable.

    GraphQL
    products.graphql
    type Query {
      products: [Product]
        @connect(# -- snip -- #)
    
      product(id: ID!): Product
        @connect(
          source: "ecomm"
          http: { GET: "/products/{$args.id}" }
        )
    }
  3. Inspect the response body for this type of request to your REST API, for example, /products/1.

    /products/1
    JSON
    1{
    2  "id": 1,
    3  "name": "Lunar Rover Wheels",
    4  "createdAt": 1675200000000,
    5  "updatedAt": 1675200000000,
    6  "description": "Innovatively designed wheels for lunar rovers, built to endure harsh moon terrain and provide optimal agility. Each wheel is constructed using advanced materials to withstand temperature fluctuations and dust.",
    7  "slug": "lunar-rover-wheels",
    8  "tags": [
    9    {
    10      "tagId": "1",
    11      "name": "Instruments"
    12    },
    13    {
    14      "tagId": "2",
    15      "name": "Space"
    16    }
    17  ],
    18  "category": "Engineering Components",
    19  "availability": "AVAILABLE",
    20  "variants": [
    21    {
    22      "name": "Standard Wheel",
    23      "price": {
    24        "original": 4999,
    25        "discounts": [],
    26        "final": 4999
    27      },
    28      "specifications": {
    29        "Material": {
    30          "value": "Titanium alloy"
    31        },
    32        "Diameter": {
    33          "value": "50 cm"
    34        }
    35      },
    36      "inventory": {
    37        "quantity": 100,
    38        "sellUnavailable": false
    39      },
    40      "shipping": {
    41        "ship1": {
    42          "weight": 5,
    43          "method": "GROUND",
    44          "estimate": {
    45            "price": 499,
    46            "arrival": 1675804800000
    47          }
    48        },
    49        "ship2": {
    50          "weight": 5,
    51          "method": "AIR",
    52          "estimate": {
    53            "price": 999,
    54            "arrival": 1675790400000
    55          }
    56        }
    57      },
    58      "upc": "0001234567890",
    59      "sku": "RW-001",
    60      "taxable": true,
    61      "variantId": "variant1"
    62    }
    63  ]
    64}
  4. Based on the response shape, complete the selection mapping. Since the response is a single object, you can directly map its fields to Product type fields without $. See the mapping overview to learn more about how to use the mapping language.

    GraphQL
    products.graphql
    type Query {
      products: [Product]
        @connect(# -- snip -- #)
    
      product(id: ID!): Product
        @connect(
          source: "ecomm"
          http: { GET: "/products/{$args.id}" }
          selection: """
          id
          name
          description
          """
        )
    }
  5. Save your products.graphql.

That's it—you've created a Connector from scratch!

Run a request

Run the following query in your Sandbox to get a single product by id:

GraphQL
Sandbox request
query Product {
  product(id: "1") {
    id
    name
    description
  }
}

You should see results for just a single product with id 1. You can update the id to view results for other products. The next Connector you'll write shows how you can orchestrate multiple REST API calls with just one GraphQL request.

Orchestrate calls with Connectors

The ecommerce demo API has a /products/:id/reviews endpoint to retrieve a particular product's reviews. Check out /products/1/reviews for an example.

/products/1/reviews
JSON
1{
2  "reviews": [
3    {
4      "id": 1,
5      "rating": 5,
6      "comment": "These wheels are perfect for my lunar exploration project!",
7      "author": "John D",
8      "createdAt": 1675200000000
9    },
10    {
11      "id": 2,
12      "rating": 4,
13      "comment": "Solid build quality, just wish they were a little lighter.",
14      "author": "Emma H",
15      "createdAt": 1675200000000
16    },
17    /// ...
18  ],
19  "summary": {
20    "total": 10,
21    "averageRating": 4
22  }
23}

This endpoint returns a list of reviews under the reviews key, each with its own ID, numerical rating, comment, author, and createdAt timestamp. To display a product detail page with reviews, clients normally have to first query the /products/:id endpoint for product details and then the /products/:id/reviews for the product's reviews.

With Connectors, the client can make one request, asking for all the information at once. Behind the scenes, the graph is orchestrating the calls and combining the information into one client-friendly response. To define what the client response should look like, you need to update the GraphQL schema:

  1. Add a new Review type underneath the Product type in your products.graphql schema.

    GraphQL
    products.graphql
    extend schema
    # -- snip -- #
    
    type Product {
      id: ID!
      name: String
      description: String
    }
    
    type Review {
      id: ID!
      rating: Float
      comment: String
    }
  2. Since one product has many reviews, you define that relationship by adding a reviews field to the Product type. It returns a list of Review objects.

    GraphQL
    products.graphql
    extend schema
    # -- snip -- #
    
    type Product {
      id: ID!
      name: String
      description: String
      reviews: [Review]
    }
    
    type Review {
      id: ID!
      rating: Float!
      comment: String
    }
  3. To retrieve a product's reviews, add a Connector to the reviews field in the Product type.

    GraphQL
    products.graphql
    extend schema
    # -- snip -- #
    
    type Product {
      id: ID!
      name: String
      description: String
      reviews: [Review]
        @connect(
          source: "ecomm"
          http: { GET: "/products/{$this.id}/reviews" }
          selection: """
          $.reviews {
            id
            rating
            comment
          }
          """
        )
    }

    What's $this? Similar to the $args variable you used to access arguments for the /products/{$args.id} path when creating a Connector for Query.product, you can use $this to access an object's fields when creating a Connector for a field on a root type like Product.

That's it! Save your updated schema to see orchestration in action.

Run a request

Run a new query in your Sandbox to get a product's details and reviews:

GraphQL
Sandbox request
query ProductWithRating {
  product(id: "1") {
    id
    name
    description
    reviews {
      id
      rating
      comment
    }
  }
}

You should see results for a single product, including its reviews. The Sandbox offers additional views in the right panel to help you understand what's happening behind the scenes.

Inspect a query plan

Click Response and select Query Plan to inspect a visual representation of the steps taken to resolve a request.

Viewing a query plan in Apollo Sandbox

A query plan is created by the query planner, the part of the GraphOS Router that efficiently breaks down a GraphQL request into multiple, coordinated calls to various endpoints and services. Despite its name, the query planner handles all types of GraphQL operations—not just queries.

Because the request requires two calls to two separate endpoints, the query planner automatically sequences API calls (the two Fetch nodes) and then combines the results (the Flatten node).

The next Connector builds on the orchestration example by combining data from two endpoints about the same object type.

Enrich entities with Connectors

If you compare the product data returned by /products and /products/1, you'll notice that /products/:id returns many more fields than /products.

Show me the data
JSON
/products/
1 {
2      "id": 1,
3      "name": "Lunar Rover Wheels",
4      "createdAt": 1636742972000,
5      "updatedAt": 1636742972000,
6      "description": "Designed for traversing the rugged terrain of the moon, these wheels provide unrivaled traction and durability. Made from a lightweight composite, they ensure your rover is agile in challenging conditions.",
7      "slug": "lunar-rover-wheels",
8      "tags": [
9        {
10          "tagId": "space",
11          "name": "Space"
12        },
13        {
14          "tagId": "engineering",
15          "name": "Engineering"
16        },
17        {
18          "tagId": "rover",
19          "name": "Rover"
20        }
21      ],
22      "category": "Engineering Components",
23      "availability": "AVAILABLE"
24    },
JSON
/products/1
1{
2  "id": 1,
3  "name": "Lunar Rover Wheels",
4  "createdAt": 1675200000000,
5  "updatedAt": 1675200000000,
6  "description": "Innovatively designed wheels for lunar rovers, built to endure harsh moon terrain and provide optimal agility. Each wheel is constructed using advanced materials to withstand temperature fluctuations and dust.",
7  "slug": "lunar-rover-wheels",
8  "tags": [
9    {
10      "tagId": "1",
11      "name": "Instruments"
12    },
13    {
14      "tagId": "2",
15      "name": "Space"
16    }
17  ],
18  "category": "Engineering Components",
19  "availability": "AVAILABLE",
20  "variants": [
21    {
22      "variantId": "variant1",
23      "name": "Standard Wheel",
24      "price": {
25        "original": 4999,
26        "discounts": [],
27        "final": 4999
28      },
29      "specifications": {
30        "Material": {
31          "value": "Titanium alloy"
32        },
33        "Diameter": {
34          "value": "50 cm"
35        }
36      },
37      "inventory": {
38        "quantity": 100,
39        "sellUnavailable": false
40      },
41      "shipping": {
42        "ship1": {
43          "weight": 5,
44          "method": "GROUND",
45          "estimate": {
46            "price": 499,
47            "arrival": 1675804800000
48          }
49        },
50        "ship2": {
51          "weight": 5,
52          "method": "AIR",
53          "estimate": {
54            "price": 999,
55            "arrival": 1675790400000
56          }
57        }
58      },
59      "upc": "0001234567890",
60      "sku": "RW-001",
61      "taxable": true
62    }
63  ]
64}

This is a common pattern in REST APIs. Product listing pages let users browse multiple products at once, while product detail pages provide more comprehensive information about individual products.

Suppose your team wants to redesign the listing page to show swatches for different product variants, but that information is only available from the product detail endpoint. You can use Connectors to still make one request for the product listing page without having to change the underlying APIs. Specifically Connectors can coordinate the following tasks:

  • Calling the product listing endpoint (/products) to retrieve all products

  • Calling the product detail endpoints (/products/:id) to retrieve variant information for all products

  • Combining the data together into one response to the client

Apollo Federation uses entity types to represent a single object type that combines information from multiple data sources.

Working with entities

An entity is any object with data fields that can be fetched with one or more unique key fields, much like objects or rows in a database. To define an entity type in a GraphQL schema, you use the @key directive, followed by the unique identifier field(s). An entity type must also specify how to retrieve the data for its fields. You can use Connectors to accomplish this.

  1. Update your schema so that it imports the @key directive. Add import: ["@key"] right below the Federation spec url:

    GraphQL
    products.graphql
    @link( # Enable this schema to use Apollo Federation features
    url: "https://specs.apollo.dev/federation/v2.10"
    import: ["@key"]
    )
  2. Update your schema so that the Product type uses the @key directive. The product's id field works well as its unique identifier.

    GraphQL
    products.graphql
    extend schema
    # -- snip -- #
    
    type Product @key(fields: "id") {
      id: ID!
      # -- all the other fields -- #
    }
  3. To the same Product type, add the variants field that's only available from the product detail endpoint. Then add the corresponding Variant type.

    GraphQL
    products.graphql
    extend schema
    # -- snip -- #
    
    type Product @key(fields: "id") {
      id: ID!
      # -- all the other fields -- #
      variants: [Variant]
    }
    
    type Variant {
      id: ID!
      name: String
    }
  4. Add the variants field to your selection mapping for the Query.product Connector.

    GraphQL
    products.graphql
    type Query {
      products: [Product]
        @connect(# -- snip -- #)
    
      product(id: ID!): Product
      @connect(
        source: "ecomm"
        http: { GET: "/products/{$args.id}" }
        selection: """
        id
        name
        description
        variants {
          id: variantId
          name
        }
        """
      )
    }
    note
    Since /products/:id returns variant IDs nested as variant.variantId, we need to rename the field to match the GraphQL schema.
  5. Finally, add entity:true to the Query.product Connector.

    GraphQL
    products.graphql
    type Query {
      products: [Product]
        @connect(# -- snip -- #)
    
      product(id: ID!): Product
      @connect(
        source: "ecomm"
        http: { GET: "/products/{$args.id}" }
        selection: """
        id
        name
        description
        variants {
          id: variantId
          name
        }
        """
        entity: true
      )
    }

The entity:true part indicates that the Connector can use the unique key (the id from the /products endpoint) to merge the correct information together. All together, these additions provide the necessary instructions for how to retrieve any product fields you want, whether you're writing a query for one product or all products.

Run a request

Run a new query in your Sandbox to get information for all products, including each product's variants:

GraphQL
Sandbox request
query ProductsWithVariants {
  products {
    id
    name
    description
    variants {
      id
      name
    }
  }
}

In the right panel, you can see all product fields returned, including the variants information, into a single response.

Schema Visualization in GraphOS Studio

By checking out the Query Plan, you can see how Connectors are fetching data from two different endpoints and merging their data intelligently into the response.

Schema Visualization in GraphOS Studio

Connectors summary

Before moving on to some additional capabilities of Rover and GraphOS, let's summarize the Connectors you've developed:

  1. The existing example Connector fetches fields from the high-level /products endpoint.

  2. The argument Connector fetches fields from the /products/:id endpoint.

  3. The first orchestration Connector adds review data to products from the /products/:id/reviews endpoint.

  4. The entities Connector combines data from /products and /products/:id endpoints for a more complete representation of the Product entity type.

Your clients can access data from all these REST endpoints with a single request to your graph. Next, you'll learn how to publish your updated schema to GraphOS so your team can access and collaborate on it.

Feedback

Forums