6. Field deprecation
5m

🗑️ Replacing a field in our schema

A common situation for an evolving data 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:

  1. Add a new field to the schema.
  2. Mark the old field as deprecated.
  3. Monitor usage of the old field.
  4. 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. Let's create a new object for the Module, and add the durationInSeconds resolver.

Inside the resolvers object in resolvers.js:

Module: {
durationInSeconds: ({ length }) => length,
},

If our GraphQL server uses a REST API as its only data source, which of the following statements are true?

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.

Illustration showing the syntax for a directive in a schema

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.

Illustration showing the syntax for the deprecated directive in a schema

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.

Illustration showing the syntax for using the deprecated directive in the schema with the reason argument
Loading...

🗑️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".

"The track's approximate length to complete, in seconds"
length: Int @deprecated(reason: "Use durationInSeconds")

We'll also update the description while we're here, replacing in minutes, which is incorrect, with in seconds.

We'll do this for both the Track and Module length fields.

In the Module type:

"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.

Code Challenge!

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 it up to GitHub, hop back to Heroku in our server app, and deploy the branch.

Task!

You're all set!

Previous
Next