How Hashnode scales custom domains feature
Introduction
Over the past year at Hashnode, we have been continuously developing GraphQL APIs to enable users to interact with their data and perform various operations. We utilize Stellate as the GQL CDN and take advantage of its Edge Caching capabilities.
Line added by Dev
Now that we are discussing Caching, it is important to emphasize a crucial aspect that follows: Cache invalidation.
Out of the methods provided by Stellate for cache invalidation, we utilize two of them:
Manual invalidation- This is performed via the Purging API provided by Stellate. We have implemented it in an event-based mechanism where specific emitted events trigger the purging of cache for a particular entity, such as a
User
or aPost
. Since this process occurs asynchronously, it is beneficial when immediate fresh data from the origin is not required.Mutation-based- This method allows for the
synchronous
purging of cache for an entity using a unique identifier, such asid
. For example, if we need to change the title of an article, we would use theupdatePost
mutation as follows:mutation UpdatePost($input: UpdatePostInput!) { updatePost(input: $input) { post { id title } } }
In the mutation response, when requesting
post
fields, we also retrieve theid
field. Thisid
enables Stellate to recognize that the next time the same entity is requested, the data should be fetched from the origin, and the cache should be updated with the new data.
The points mentioned here can possibly extend to any GraphQL CDN caching service you may use. Requesting an unique identifier like id
in your Queries
, Fragments
, or Mutation responses
becomes essential when there is a caching layer between your users and the server. Without the identifier, you might encounter stale
results.
The problem
As we delved into using GraphQL queries and mutations to build features, we encountered numerous bugs. The primary cause was the absence of the id
in the queries and mutation responses. This issue becomes evident when performing a mutation and expecting the subsequent query to return fresh results.
Reminding developers to always remember to retrieve the id
field was not a sustainable solution, given our fast-paced development and how easy it is to overlook.
The solution
We needed a more reliable method to ensure developers include the id
field before merging their code. The ideal place for this check is in our CI/CD pipeline. Our linting rules run within a GitHub action, and if these rules are not followed, the developer must correct the issue before they can proceed with merging their changes.
Implementing a linting rule to notify developers about missing id fields appeared to be the most effective solution. Consequently, we began researching existing rules and discovered the eslint-graphql plugin.
This plugin offers a wide array of rules that can be applied to lint both GraphQL schema and GraphQL operations. One rule that proved particularly useful to us was require-id-when-available. The name itself indicates its functionality. Although this rule is marked as deprecated in the documentation, its renamed version, require-selections, is currently available only in the pre-release
version.
Configuring the plugin and the rule can be accomplished by adding it to the overrides
list in your eslint configuration file (.eslintrc.json
in our setup):
{
"files": ["*.graphql"],
"parser": "@graphql-eslint/eslint-plugin",
"plugins": ["@graphql-eslint"],
"parserOptions": {
"skipGraphQLConfig": true,
"schema": "https://gql.hashnode.com",
"operations": "**/*.graphql"
},
"rules": {
"@graphql-eslint/require-id-when-available": "error",
}
}
By default, the plugin will search for a schema
file, which can be a .graphql
or other supported extensions (such as .json
) that you may have configured. In our case, we had the schema file locally but did not push it to the remote repository (it was ignored using .gitignore
). Therefore, in the parserOptions
, we specified skipGraphQLConfig
as true
and set the schema
field to our official GQL URL.
Not all rules in this plugin require the GQL schema, but require-id-when-available
does need to know where to expect the id
field in the GraphQL documents.
Here is how the plugin detects and reports the missing id
:
Conclusion
When we configured the plugin, we discovered 9
more places where we missed retrieving the id
field. This plugin is set to significantly reduce the time we spend on debugging caching-related issues, allowing us to dedicate more time to building exciting new features for our users.