5. Referencing an entity
5m

Overview

Remember our dream ?

query GetRecipeAndCookwareInformation {
recipe(id: "rec3j49yFpY2uRNM1") {
name
description
ingredients {
text
}
instructions
cookware {
name
description
cleaningInstructions
}
}
}

This requires a change in the recipes : adding a cookware to the Recipe type, that returns a list of Cookware types. The recipes has information available about the names of cookware a recipes uses, but the schema doesn't allow us to access it.

The recipes also has no idea what the Cookware type is! Let's fix that.

In this lesson, we will:

  • Learn what an type is and where to find entities in our using Studio
  • Reference an existing defined in another

What's an entity?

An entity is an with split between multiple .

We already have an in our Poetic Plates . Let's head over to Studio to our supergraph page and navigate to the Schema page.

Select Objects from the left-hand list. See that green E label beside the Cookware type? E stands for !

https://studio.apollographql.com/

Schema reference page for Objects in Studio

Note: We saw that same label in Explorer, in the Documentation panel, beside any that returned the Cookware type.

Because it was defined in the kitchenware , which we didn't clone or run locally, we didn't really see what was happening behind the scenes in the schema file. But that's okay, we actually didn't need to: Studio helped us identify what types were available to us!

You can still find the schema file here, if you're interested.

With entities, each can do one or both of the following:

  • Contribute different to the
  • Reference an , which means using it as a return type for another defined in the

In this course, we'll focus only on learning how to reference an . If you're curious to dive deeper into entities and more federation-specific concepts, you can check out the Voyage series.

Referencing an entity

The Cookware type is an ; we know that we can reference an entity, which means using it as a return type for another . That's exactly what we want to do with the Recipe.cookware ! Let's try it out.

First, make sure both rover dev processes are still running, as well as the recipes .

Next, let's open up the schema.graphql file in our recipes .

Scroll down to find the Recipe type and add the new at the end.

schema.graphql
type Recipe {
# ... other Recipe fields
"List of cookware used in the recipe"
cookware: [Cookware]
}

Save your changes! The first rover dev process is listening for changes to that schema.graphql file; when it detects changes, it will re-compose the local . Let's check out the main rover dev process... oh no! An error!

✨ change detected in ./schema.graphql...
🔃 updating the schema for the 'recipes' subgraph in the session
🎶 composing supergraph with Federation v2.3.1
💀 composition failed, killing the router
error[E029]: Encountered 1 build error while trying to build a supergraph.
Caused by:
UNKNOWN: [recipes] Unknown type Cookware
The subgraph schemas you provided are incompatible with each other. See https://www.apollographql.com/docs/federation/errors/ for more information on resolving build errors.

We've got a build error "Unknown type Cookware".

That makes sense! We haven't given the recipes any definition for Cookware, so it doesn't understand what we're referring to yet. Let's go ahead and fix that.

In the same schema file, just below the Recipe type, add the Cookware type. Specifically, we're going to add a stub of the Cookware , which contains the minimum the recipes needs to reference it.

To define the stub, we'll add the @key after the type definition. This directive is used to define the 's primary key, which uniquely identifies an instance of an entity. (Giving us a way to tell an object representing a “cast iron skillet” apart from one representing a “wok”, for example!)

schema.graphql
type Cookware @key {
}

We can find the Cookware type's primary key using Studio. Back in the Schema page, with the Objects list selected, click the Cookware type to see its details. We'll see the name listed under the Keys section.

Let's jump back to the schema.graphql file. The @key needs a fields property, which is set to its primary key (or keys!). In this case, we'll set the fields property to name. Additionally, we'll add the name inside the type definition, which returns a non-nullable String.

schema.graphql
type Cookware @key(fields: "name") {
name: String!
}

Lastly, since the doesn't contribute any additional to the (we're only referencing the entity using its name , not contributing fields to it!), we need to add one more thing. Inside the @key , add another for resolvable and set it to false.

schema.graphql
type Cookware @key(fields: "name", resolvable: false) {
name: String!
}

This indicates that the recipes doesn't define a reference for the Cookware . Again, if you're curious to dive deeper into entities and more federation-specific concepts, you can check out the Voyage series!

Let's save our changes and check out the rover dev output again.

✨ change detected in ./schema.graphql...
🔃 updating the schema for the 'recipes' subgraph in the session
🎶 composing supergraph with Federation v2.3.1
✅ successfully composed after updating the 'recipes' subgraph

All is well! Our schema changes have successfully been composed with the other and the is working smoothly.

So... we're all done right? 🤔 Let's jump over back to the Sandbox tab which is connected to our locally-running router on http://localhost:4000 and try to run the dream .

query GetRecipeAndCookwareInformation {
recipe(id: "rec3j49yFpY2uRNM1") {
name
description
ingredients {
text
}
instructions
cookware {
name
description
cleaningInstructions
}
}
}

We've got the recipe's details... but scrolling down to the cookware portion of our data... we're seeing null!

Now, it's possible there's just no available information about cast iron skillets. Let's try for it directly using the cookware .

query GetCookware {
cookware(name: "cast iron skillet") {
name
description
cleaningInstructions
}
}

Looks like we get data back! So... what's the problem?

Resolving the cookware field

Our resolves data for a specific recipe, but when it gets to the Recipe type's cookware and tries to resolve it... it has no idea what to do! That's because we haven't written the function for the Recipe.cookware ! Without a function, the recipes doesn't know what data to return – or even where to find it – when we for a particular recipe's cookware. Let's fix that!

In the src/resolvers/Recipe.js file, add the following function for the cookware .

src/resolvers/Recipe.js
cookware(recipe, _, { dataSources }) {
const cookwareNamesList = dataSources.recipesAPI.getRecipeCookware(recipe.id);
if (!cookwareNamesList) return;
return cookwareNamesList.map((c) => ({
name: c,
}));
},

What's happening here? Let's break it down.

First, the parameters of the . We're using the first parameter parent (renamed as recipe) and the third parameter contextValue (destructured to access dataSources).

Next, inside the body of the function, we're using the dataSource method getRecipeCookware to retrieve a list of the recipe's cookware. This method was already implemented for us.

In the event that no cookware list was available for that recipe (it's possible that it just doesn't use any, or it is missing that data!), we'll return early.

Otherwise, we'll map over the list and for each item, return an object. This object is called an entity representation. It's what the uses to represent a specific instance of an . A representation always includes the typename for that and the @key for the specific instance.

  • The __typename : This field exists on all types automatically. It always returns the name of its containing type, as a string. For example, Cookware.__typename returns "Cookware".
  • The @key : defines the 's primary key, which uniquely identifies an instance of an entity. We know that the Cookware type's primary key is its name .

So, going back to the last line in the Recipe.cookware :

src/resolvers/Recipe.js
return cookwareNamesList.map((c) => ({
name: c,
}));

In this case, we've omitted the __typename because takes care of returning that automatically. The cookwareNamesList only has a list of Strings, so we can't simply return that list, as-is. The map function takes care of transforming each item in the list into the shape the is expecting.

Testing with the local router

Alright, we've got the code changes to back our schema changes! Let's jump back over to Sandbox to check on our local . We should be getting the full data back for our dream .

query GetRecipeAndCookwareInformation {
recipe(id: "rec3j49yFpY2uRNM1") {
name
description
ingredients {
text
}
instructions
cookware {
name
description
cleaningInstructions
}
}
}

Fantastic, we've got it!

Practice

Use the incomplete schema below to answer the next question.

type Fruit {
id: ID!
name: String
soldBy: Vendor
}
??? {
vendorId: ID!
}
Which of the following code snippets should be on line 7 to replace the question marks?

Key takeaways

  • An entity is an with split between multiple . A subgraph can contribute fields to an or simply reference it.
  • Referencing an means using it as a return type for a defined in the . To reference an entity, the subgraph must include a stub of the entity definition containing the entity's @key, which includes the 's primary (s) and the resolvable property set to false.
  • Additions to the schema may compose successfully, but don't forget to add all code necessary to resolve the new !

Up next

Everything is working great locally! Let's push these changes to and get our production up to date.

Previous

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.