Back to Blog
Lesson 48 of the GraphQL: Your First GraphQL Schema & Server course
API ArchitectureSeptember 4, 20264 min read

GraphQL API Versioning: Deprecate Fields and Evolve Your Schema

Learn how to evolve your GraphQL API without breaking changes. Master the expand-and-contract pattern, adding new fields and using @deprecated to sunset data.

GraphQLAPI DesignVersioningDeprecationBackend Development
A close-up view of PHP code displayed on a computer screen, highlighting programming and development concepts.

Previously in this course, we covered Deploying the Server: GraphQL Production and Cloud Hosting, moving your project from local development to the real world. Now that your API is live, you need a strategy for change. Unlike REST, where you might see /api/v1/ or /api/v2/ in your URL paths, GraphQL encourages continuous evolution on a single endpoint.

In this lesson, we will implement the "expand-and-contract" pattern to evolve your API without forcing breaking changes on your clients.

The Philosophy of API Evolution

In REST, you often solve the problem of breaking changes by versioning the entire API. This is why you see /v1/ and /v2/ in endpoints. This approach is heavy; it forces you to maintain two separate codebases.

GraphQL takes a different approach. Because clients explicitly request only the data they need, you can add new fields to your types without affecting existing queries. The schema is intended to be a living, growing contract. When you need to change a field, you don't remove it—you evolve it.

The Expand-and-Contract Pattern

A detailed close-up of a printed contract document on a wooden table surface.

The standard lifecycle for changing a field involves three steps:

  1. Expand: Add the new field to your schema while keeping the old one.
  2. Warn: Use the @deprecated directive to signal to developers that the old field is being retired.
  3. Contract: After monitoring your analytics and ensuring no traffic hits the old field, remove it.

Worked Example: Updating a User Model

Let’s say our User type currently has a name field, but we want to split this into firstName and lastName to provide better granularity.

1. Expand

We add the new fields to our existing type definition in our SDL:

GraphQL
type User {
  id: ID!
  name: String # We'll deprecate this soon
  firstName: String
  lastName: String
}

We then update our resolver to populate these new fields. The client can now start migrating their queries to the new structure at their own pace.

2. Warn (Deprecation)

Once the new fields are available, we tell the world the old field is on its way out. GraphQL provides a built-in directive called @deprecated that allows you to specify a reason.

GraphQL
type User {
  id: ID!
  name: String @deprecated(reason: "Use firstName and lastName instead")
  firstName: String
  lastName: String
}

When a developer uses a tool like Apollo Sandbox or a modern IDE, the name field will now appear with a strikethrough, and the reason will show up in the documentation.

Hands-on Exercise: Deprecating a Field

Open your project's typeDefs.js. Find an existing field that you'd like to "refactor" (for example, renaming title to header).

  1. Add the new field (header) to your schema.
  2. Apply the @deprecated directive to the title field.
  3. Update your resolver to return the same data for both fields temporarily.
  4. Run your server and inspect the documentation in your GraphQL explorer to see the deprecation message in action.

Common Pitfalls

  • Removing fields prematurely: Never delete a field without checking your query logs. Even if you think no one is using it, you might be surprised.
  • Ignoring the reason: When using @deprecated, always provide a clear reason. It serves as your primary communication channel with the developers consuming your API.
  • Forgetting to update documentation: While the schema is self-documenting, it’s helpful to maintain a changelog alongside your code.

FAQ

Q: Can I change the type of a field? A: Changing a field's type is a breaking change. Instead, create a new field with a new name (e.g., priceInt vs priceString) and deprecate the old one.

Q: How do I know when it's safe to delete a deprecated field? A: Use server-side logging or analytics tools like Apollo Studio to monitor which fields are still being requested. When usage drops to zero, it's safe to remove.

Q: Does @deprecated actually stop the code from working? A: No, it is purely a metadata flag. It tells developers, "Please stop using this," but the resolver will continue to function until you physically remove it from the code.

Recap

Team members presenting a project in a modern office setting with a focus on collaboration.

API evolution in GraphQL centers on the idea of non-breaking additive changes. By using the @deprecated directive, you communicate changes clearly, allowing your consumers to migrate their codebases safely. This keeps your API clean, performant, and stable for all users.

Up next: We will explore how to use schema directives to further document and enforce rules within your API.

Similar Posts