13. Contributing to an entity


In the previous lesson, we just saw how can reference an as a 's return type. Now, let's take a look at how can contribute to an .

In this lesson, we will:

  • Learn how multiple can contribute to an
  • Update the Location in our reviews by contributing the reviewsForLocation and overallRating s
The FlyBy schema diagram, with pencil icons next to the Location.reviews and Location.overallRating fields

✏️ Contributing fields

Remembering our FlyBy UI, we know it needs to fetch each location's overallRating, along with a list of its reviewsForLocation:

A query containing fields that will be populated by our two subgraphs, locations and reviews

By following the separation of concerns principle, it makes sense that any data about ratings or reviews is populated by the reviews , so let's head on over there and make those additions!

  1. Open up the subgraph-reviews/reviews.graphql file.

  2. Find the Location definition in the schema. We previously defined this as a stub of the Location type.

    type Location @key(fields: "id", resolvable: false) {
    id: ID!

    By default, a should only contribute that aren't defined by other subgraphs, with the exception of the primary key field. This means that because the locations defines name, description, and photo as for the Location type, we won't - and shouldn't - define those here in the reviews !

    Note: You can override the default behavior explained above to allow multiple subgraphs to resolve the same field by applying either the @shareable or @provides . This is an optional performance optimization that can instruct the on how to plan the execution of a across as few as possible.

    Because we now want the reviews to contribute new to the Location definition, the first thing we need to do is remove the resolvable: false property from the @key . This will enable our reviews to define and resolve its own Location .

  3. Remove the resolvable: false property from the Location type's @key .

    type Location @key(fields: "id") {
    id: ID!

✏️ Adding new fields to the Location entity

Now we're ready to add the two new .

  • the overallRating , which returns a Float
  • the reviewsForLocation , which returns a non-null list of Review objects.

We'll also add descriptions to these so we can quickly see what they represent.

type Location @key(fields: "id") {
id: ID!
"The calculated overall rating based on all reviews"
overallRating: Float
"All submitted reviews about this location"
reviewsForLocation: [Review]!

✏️ Adding resolvers

Each of these needs a function to return data, so let's take care of that next.

  1. Open the resolvers.js file in the subgraph-reviews directory.

  2. Add a Location entry to the resolvers map. We'll also add two empty functions for the overallRating and reviewsForLocation .

    const resolvers = {
    Query: {
    // ...
    Location: {
    overallRating: () => {},
    reviewsForLocation: () => {},
    Review: {
    // ...
    Mutation: {
    // ...
  3. We'll start with the overallRating . First, we'll destructure the parent (a Location object) to get the id . We'll also destructure the contextValue to pull out our dataSources.

    overallRating: ({id}, _, {dataSources}) => {
    // TODO
  4. Inside the function, we'll return the results of calling our dataSources object, its ReviewsAPI, and its getOverallRatingForLocation method. Then, pass in the id of the location that we're .

    overallRating: ({id}, _, {dataSources}) => {
    return dataSources.reviewsAPI.getOverallRatingForLocation(id);

    Note: You can check out how the getOverallRatingForLocation method works by peeking inside the subgraph-reviews/datasources/ReviewsApi.js file.

  5. Next, we'll set up the function for the reviewsForLocation and follow the same structure as before. This time, we'll use the getReviewsForLocation method of the ReviewsAPI to fetch all reviews for a location based on its id.

    reviewsForLocation: ({id}, _, {dataSources}) => {
    return dataSources.reviewsAPI.getReviewsForLocation(id);

Wonderful! Our receive a location's id and can return the right data for that location.

Adding a __resolveReference function

Earlier, we learned that each that contributes to an needs to define a reference for that entity.

We already defined the reference in the locations , but the reviews also needs some way of knowing which particular location object it's resolving for.

Here's the good news: because we're using , defining the reference function explicitly in the reviews is not a requirement. defines a default reference for any entities we don't define one for.

This diagram shows how the __resolveReference function works by default with a for a particular Location.

The reviews subgraph resolve reference function called for a particular queried Location
  1. A queried location is resolved in the locations based on its id .
  2. When the server reaches the reviewsForLocation , the knows that this is the responsibility of the reviews . The __resolveReference function receives the queried Location object that the locations returned.
  3. The reviewsForLocation receives the referenced Location object as its parent , which it can then destructure and use to resolve data.

Even with this feature working for us under the hood, we'll walk through the steps to add this function ourselves in the optional section below, and review what happens when our associates data across .

So are we ready to put our to the test? Not so fast! Remember we made schema changes! These changes need to be published to the registry, or we'll run into the same problem we faced in the last lesson.

So we'll run the rover subgraph publish command, passing it the values for our reviews .

rover subgraph publish <APOLLO_GRAPH_REF> \
--name reviews \
--schema ./subgraph-reviews/reviews.graphql

Querying data across subgraphs

With a successful publish, let's return to Studio and refresh the Explorer. We can see that our list of sub now includes overallRating and reviewsForLocation!

The Location type with reviews fields

Let's include these in a new to our . We'll use the query the client needs for the location details page.

query GetLocationDetails($locationId: ID!) {
location(id: $locationId) {
reviewsForLocation {

In the Variables panel:

{ "locationId": "loc-1" }

Look at this sweet sweet data!

The client is going to be thrilled that they're getting information about a location from both without having to do any work to put it all together themselves!

Even better, we didn't have to restart our . This is thanks to our router's connection to Apollo Uplink. Our newly published triggered Apollo Studio to compose a new . Our router then polled the Uplink and fetched the new supergraph schema. Best of all, the router started to use the new supergraph schema immediately, enabling us to the new right away!

With that, we can finally check off the last two of our schema agreement!

The FlyBy schema diagram, with all the fields checked off!


Check your understanding!
In a federated graph, we should define our subgraphs based on 
instead of 
. Types containing fields that can be resolved across multiple subgraphs are called 
. These types always have a 
 that enables different subgraphs to associate data with the same object. To keep its supergraph schema up to date, our 
 can poll the 

Drag items from this box to the blanks above

  • concerns

  • entities

  • key field

  • create

  • endpoints

  • router

  • delete

  • types

  • assignments

Code Challenge!

You're working on a federated graph that manages book information. The authors subgraph defines an Author entity with a primary key field id of non-nullable type ID. You want to use the Author entity in the books subgraph. Define the Author entity below, and add a new field, books, which returns a non-nullable list of non-nullable type Book.

Key takeaways

  • A that contributes to an should define the following:
    • The , using the @key and its primary key , as well as the new fields the defines
    • A __resolveReference function to know which particular instance a is resolving for. This can be taken care of by default by .
  • A federated architecture helps organize and illustrate the relationships between types across our in a way that an app developer (or multiple teams of developers!) would want to consume the data.
  • When both use the same primary key to associate data for a type, the coordinates data from both sources and bundles it up in a single response.

Up next

Congratulations, you've finished implementing all the in the FlyBy !

In the next and final lesson, we'll put the backend and frontend together and finally see FlyBy working in a browser!


Share your questions and comments about this lesson

Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.