Using the Sunset header with GraphQL
Try as we might to design APIs that last, it is very difficult to completely avoid the need for breaking changes. The Sunset
HTTP header offers a standardized way to inform clients about the deprecation of API endpoints in a programmatically interpretable way. In my previous post on this topic I discussed what this header is and why it’s important for managing API lifecycles—but didn’t explain how to use it.
I’ve since published a package called graphql-sunset
which lets you sunset parts of your schema with no code changes. Let’s take a look at how it can be used.
Table of contents
graphql-sunset
with Apollo Server
Integrating Install the plugin
Using your favorite JavaScript package manager, install the graphql-sunset
package:
$ pnpm add graphql-sunset
Then, import the Apollo server plugin and add it to your server’s plugins
array:
import { ApolloSunsetPlugin } from 'graphql-sunset'; // ... const server = new ApolloServer({ // ... plugins: [ new ApolloSunsetPlugin(), ],});
@sunset
directive
Define the Fields are marked for sunset by applying a @sunset
directive. Because it’s possible to follow either a code-first or schema-first approach to defining your schema, the plugin itself doesn’t define this for you. Out of the box, the plugin expects the directive to take two arguments:
url
: This will be used to fill in theLink
header.when
: This should be an RFC3339 date time string. It will be used to populate theSunset
header itself.
The ApolloSunsetPlugin
constructor takes an options bag which can be used to tweak these defaults. Assuming you are happy with these, however, you’ll want to add the following to your schema:
directive @sunset( url: String! when: String!) on ARGUMENT_DEFINITION | FIELD_DEFINITION | INPUT_FIELD_DEFINITION
Mark fields for sunsetting
WIth this in place, you can now go ahead and start marking fields in your schema for upcoming removal.
Let’s say that we currently have a getUser
query field that we want to remove in favor of user
, following my GraphQL naming conventions. We’ll remove the old field at the start of October 2024, by annotating the query field with a @sunset
directive like so:
type Query { getUser(id: ID!): User @sunset( url: "https://docs.sophias-api.com/notices/removing-getuser" when: "2024-10-01T00:00:00.000Z" ) user(id: ID!): User}
Test it
Executing a GraphQL operation which selects the getUser
query field will cause us to receive Sunset
and Link
headers, just like we expect to:
$ curl --include --request POST \ --header 'content-type: application/json' \ --url http://localhost:3000/graphql \ --data '{"query":"query { getUser(id: \"user_abc\") { id } }"}'HTTP/1.1 200 OK...sunset: Tue, 01 Oct 2024 00:00:00 GMTlink: <https://docs.sophias-api.com/notices/removing-getuser>; rel="sunset"...
Notes
If the consumer were to request multiple fields which are being sunset, then the Sunset
header will be set to the earliest sunset date. The Link
header will include links to all the associated sunset URLs. This is handy, because it lets consumers track metrics such as the average number of deprecated resources they are requesting per GraphQL operation.
If the server already had a Link
header set, then this package will preserve the existing Link
header value(s) and merge in the sunset links, ensuring that no information is lost.
Finally, it’s important to note that graphql-sunset
is a purely informational tool. It doesn’t handle the dirty work of actually removing the deprecated functionality from your API at the time of sunsetting. That responsibility still lies with you, the API designer.
Use cases
The main use case here is to employ something like WeWork’s faraday-sunset
library to automatically alert on the client side about impending breaking changes. Getting consumers to migrate their code in time is really hard, especially in a context where your consumers are external to your organization. Even prior to thinking about actioning any code changes, it can be hard to just communicate the breaking change.
Because the Sunset
header is programmatically interpretable, it’s possible to build really nice automated workflows around it. An upcoming version of Rye’s SDK will likely start doing this, because managing breaking changes has been a recurring problem for us as we build out new functionality and run up against the limits of our abstractions.
There are other ways you can achieve this, but the great thing about the Sunset
header is that it’s standardized. There might not be a lot of tooling around it today, but I expect that to quickly change. One of the things I really love about GraphQL compared to RESTful API architectures is that GraphQL has a rigorously defined spec that helps homogenize API behavior.
On the other hand, every REST service feels like a snowflake because there are very few guardrails in place to prevent API designers from doing whatever they like. Standardization is a good thing that makes developers more productive—we need more of it.
Conclusion
Integrating the Sunset header into your Apollo GraphQL server using graphql-sunset
is a proactive step toward better API management. By providing clear deprecation notices, you can improve the developer experience and ensure a smoother transition for your API clients.
For more details, check out the graphql-sunset
GitHub repository.
Feel free to reach out with any questions or feedback!