🗑️ Replacing a field in our schema
A common situation for an evolving graph is to deprecate an existing schema field in favor of a new field. Let's see how to do this in our Catstronauts schema.
As we were going through some code cleanup, we noticed that the length
of a Track
and a Module
is documented to be in minutes. After some digging though, it looks like this is incorrect, and the value returned from our REST API is actually in seconds! The client app is doing the work to transform this number into minutes.
We should fix the description in the schema so that future clients won't be misled. We can also improve our schema to be more explicit about what exactly this field is.
This requires changes to our schema! 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
First, let's add the new field to our schema. Open up the server
repo.
In the src
folder, in schema.js
, we'll add a new field in the Track
type called durationInSeconds
. This field name is much clearer. This returns an Int
type, and we'll also give it an accurate description.
"The track's full duration, in seconds"durationInSeconds: Int
We'll do the same for the Module
type.
"The module's video duration, in seconds"durationInSeconds: Int
✍️ Adding resolvers
We'll need a resolver for both of these new fields. Our REST API doesn't provide this durationInSeconds
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 resolvers.js
file. Let's tackle the Track.durationInSeconds
resolver first.
Add a new property under the Track
object with the same name as the field, durationInSeconds
. This will be set to our resolver function.
const resolvers = {Query: {/* query resolvers */},Mutation: {/* mutation resolvers */},Track: {/* Track.author and Track.modules resolvers */durationInSeconds: () => {},},};
Our schema is expecting an Int
value to be returned here. We're going to take the length
value and map it to this new field. We can access the length
value using the first parameter of the resolver, the parent
, because of the resolver chain.
In the durationInSeconds
resolver, we'll destructure the first parameter for the length
property. We don't need any of the other resolver parameters, and we'll return that length
value right away in the body of the resolver.
durationInSeconds: ({ length }) => length,
We'll do the same with the Module.durationInSeconds
resolver. Inside the resolvers
object in the resolvers.js
file, let's create a new object for the Module
, and add the durationInSeconds
resolver:
Module: {durationInSeconds: ({ length }) => length,},
That's our new field taken care of. Next, we need to mark the length
field as deprecated.
↔️ Schema directives
To mark a field as deprecated, we'll use GraphQL schema directives. A schema directive is indicated with an @
character, and it decorates a specific symbol in your schema, such as a type or field definition.
Our server (or any other system that interacts with our schema) can then perform custom logic for that symbol based on its directives. 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.
Drag items from this box to the blanks above
@deprecated
@deleted
!
message
@
types
fields
description
reason
resolvers
🗑️Using the @deprecated
directive
Open up the schema.js
file again. In the Track
type, after the return type of the length
field, we'll add the @deprecated
directive, with the reason
as "Use durationInSeconds". We'll also update the description to say "in seconds" (instead of "in minutes", which is incorrect).
The Track.length
field should now look like this:
"The track's approximate length to complete, in seconds"length: Int @deprecated(reason: "Use durationInSeconds")
Let's make the same update to the Module.length
field:
"The module's length in seconds"length: Int @deprecated(reason: "Use durationInSeconds")
And there we go, this was the last change we had to make on the server side!
If you're coding along on your local project, make sure to test your API change before moving on, to confirm you can query the new durationInSeconds
field on both track and module.
Let's make some schema changes! We're replacing a Spaceship's missionCount
field with completedMissionsCount
. Deprecate the missionCount
field, giving it a reason
argument to use completedMissionsCount
. Add a new field for completedMissionsCount
, which is a nullable Int
. Document a helpful description for this field, reusing the same description from missionCount
.
After we've confirmed everything works locally, we're ready to push our change to production. You know the drill: add and commit our changes, push them up to GitHub, and wait for Railway to deploy the latest version of our server app.
You're all set!
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.