Overview
Let's begin with the first change we want to make to our supergraph.
In this lesson, we will:
- Learn the process for replacing a field in our schema
- Learn what a GraphQL directive is used for
- Use the
@deprecated
directive for a field in our schema
The schema so far
A common situation for an evolving graph is to deprecate an existing field in the schema in favor of a new field. Let's investigate one such situation in our Poetic Plates supergraph.
Here's what the schema looks like for the Ingredient
type:
type Ingredient {id: ID!"Display text for the ingredient"text: String"The name of an ingredient"name: String"A potential substitute for the ingredient, if available"substitute: String}
Users have shared feedback about their confusion related to the text
and name
fields of an ingredient.
Let's take a look at what values those fields return. Using your supergraph's Explorer, run the following query to retrieve the list of ingredients for a random recipe.
query GetRandomRecipeIngredients {randomRecipe {ingredients {textname}}}
Depending on which recipe you received randomly, your response may be different, but here's a snippet of what we received:
{"data": {"randomRecipe": {"ingredients": [{ "text": "1 cup onions, medium diced", "name": "onions" },{ "text": "2 tbsp olive oil", "name": "olive oil" }// ...]}}}
Can you see the difference between text
and name
?
It looks like the text
field returns a longer, more detailed version about the ingredient. The name
field simply returns what the ingredient is, without any additional details like quantity or how it's prepared.
Digging deeper into each field's description in the schema, it isn't clear from the documentation that this is what users should expect. So we can definitely understand the confusion about which field they should be using for their purposes.
We should fix the description in the schema so that it's more clear what each field is expected to return. We can also improve our schema to be more explicit about what exactly the text
field returns. Maybe there's a better name for this field!
This requires changes to our schema, but we don't want to remove the text
field and replace it with a new one all in one go. That would break existing queries clients are sending, and we don't want to alarm anyone!
So here's the plan:
- Add a new field to the schema.
- Mark the old field as deprecated.
- Monitor usage of the old field.
- Whenever usage is down and clients have had appropriate time to make their changes, we can safely remove the old field!
Let's get to it!
Adding a new field to the schema
First, let's add the new field to our schema.
Open up the recipes subgraph repostiory in your code editor and navigate to the
schema.graphql
file.Find the
Ingredient
type, and add a new field calleddetailedDescription
. This field name is much clearer thantext
! This returns aString
type, and we'll also give it an accurate description, complete with an example.schema.graphql"""A detailed description of the ingredient needed in the recipe.Usually in the format of: <QUANTITY> <INGREDIENT NAME>, <SPECIAL INSTRUCTIONS>.For example: 1 cup onions, medium diced"""detailedDescription: String
Adding a new resolver
We'll need a resolver for this new field. Our data source doesn't provide this detailedDescription
property. That's okay—it doesn't need to match our schema's fields 1 to 1. That's the beauty of GraphQL!
Open up the
src/resolvers/Ingredient.js
file.Add a new property under the
Ingredient
object with the same name as the field,detailedDescription
. This will be set to our resolver function.src/resolvers/Ingredient.jsmodule.exports = {Ingredient: {detailedDescription: () => {},},};Remember, this field will be the exact same value as the
text
field. So we'll want to take thattext
value and return it from thedetailedDescription
resolver.We can access the
text
value using the first parameter of the resolver, theparent
, because of the resolver chain.In the
detailedDescription
resolver, we'll destructure the first parameter (parent
) for thetext
property. We don't need any of the other resolver parameters, and we'll return thattext
value right away in the body of the resolver.src/resolvers/Ingredient.jsdetailedDescription: ({ text }) => text,That's our new field taken care of!
Testing our changes
Let's make sure that the new schema addition is working properly.
If your server isn't running, you can start it with npm run dev
. This will start the server at http://localhost:4001. Let's head over there in the browser to test out a query with Sandbox.
We should see the new field detailedDescription
show up on the Documentation panel on the left.
Let's try this query that retrieves a random recipe and its ingredients: both the text
and detailedDescription
fields.
query GetRandomRecipeIngredients {randomRecipe {ingredients {textdetailedDescription}}}
When we get data back, we should see that the values for both the text
and detailedDescription
fields are the same.
Everything's working as expected! Onwards with our plan, we need to mark the text
field as deprecated.
The @deprecated
directive
For our use case, we'll use one of GraphQL's default directives: @deprecated
.
We apply the @deprecated
directive to a field to indicate that the field is... deprecated! We should always pass this directive a reason
argument, which indicates why it's being deprecated, and which field a client should use instead. This is useful information for the clients querying your graph.
Using the @deprecated
directive
Open up the
schema.graphql
file again.Find the
Ingredient
type'stext
field. After the return type of thetext
field, we'll add the@deprecated
directive, with thereason
as"Use detailedDescription"
.The
Ingredient.text
field should now look like this:schema.graphql"Display text for the ingredient"text: String @deprecated(reason: "Use detailedDescription")
And there we go!
Testing our changes
So what's changed? Let's find out! Your server should still be running and pulling the latest changes (if not, make sure you run npm run dev
again). Head back over to http://localhost:4001.
Looking at the same query we had earlier, we can now see a couple changes!
First, the text
field is showing up with a yellow squiggly underline, and if we hover over it, we'll see why! The field is deprecated, just like we marked in our schema. We can also see the same message on the Documentation panel on the left.
We can still run the query with no issues, but the warning is meant to let us know that we really shouldn't be including the text
field in our queries anymore.
Practice
Drag items from this box to the blanks above
@deprecated
resolvers
description
fields
reason
@
types
@deleted
message
!
Key takeaways
- To replace a field in our schema, we should:
- Add the new field
- Mark the old field as deprecated
- Monitor usage of the old field
- Whenever usage is down and clients have had appropriate time to make their changes, we can safely remove the old field!
- A schema directive is indicated with an
@
character, and it decorates a specific symbol in your schema, such as a type or field definition. - To deprecate a field, we use the default GraphQL directive
@deprecated
and thereason
argument to let our graph consumers know why and which field to use instead. - We can still run queries with deprecated fields, but the deprecation status is a cue for us to stop using that field in existing queries.
Up next
We've completed the first two steps of our plan, but that was all in our local environment. We need to let GraphOS know about these changes!
In the next two lessons, we'll take a look at how we can land these changes safely and confidently using schema checks and launches.
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.