Persisted GraphQL Query with axios and SilverStripe 4

The original idea is from this blog, but I prefer a more clear and simpler way to describe and implement this feature, so there is a blog post here :)

At the very beginning, let’s review how we utilize GraphQL over HTTP(s) protocol in its essential:

  • We have a /graphql endpoint
    - It receives 2 parameters: query and variables
    - It can be revoked by GET or POST

So, without any overhead, we could just send plain HTTP(s) request via axios library or even fetch as usual (no need to introduce Apollo — the big monster):

As we can see clearly, it is pretty straightforward to write a factory class to generate an axios instance with the necessary data carried every time we want to make a request.

Then it is the time to play with Persisted GraphQL Query. But what it is?

In a nutshell: turn your query into an id and send it with variables to the /graphql endpoint, so no query parameter would be passed to the backend.

You might be curious about why we should do this. Well, currently I can come up with several reasons but I am sure the list should be expended:

  • If you want to whitelist your api calls with GraphQL (prevent your GraphQL api from being invoked by malicious users: some queries may increase your database pressure or be harmful in security aspect), you may want to identify queries by id, not full query text

So far, we got the basic idea of Persisted GraphQL Query, then let’s have look at how to implement it in our application.

Basically, there are 4 steps we need to do (assume that we have already put our query text in .graphql files, if this is not the case, we should split our queries from javascript/typescript files into dedicated graphql files):

  1. Collect our *.graphql files and generate a mapping.json file
    - key: compressed query text
    - value: id (uuid is the preferred format)

Let’s go through a step-by-step process for more details.

As Step 0, you should already organized your *.graphql files like below:

Notice: it is not necessary to put everything in the same level. That is, feel free to organize those files in a hierarchical folder structure if you wish.

Step 1: use this open-source library to extract queries from those *.graphql files and generate the mapping file. The instructions are listed below:

npm install -g graphql-extractor

graphql-extract ./graphql/ ./graphql/mapping.json

Step 2: add the pre-process code to the AxiosService class:

A little trick here is that we compress the query been passed into the factory method to maintain the consistent with the mapping file (queries in the mapping file are compressed too). So in this case, once you pass a query that already exists in the mapping file, axios will use id to do the request instead of query text (the other parts of your code keep unchanged).

Step 3: This step depends on your preference or need. Currently, you can embed it in YAML config file of SilverStripe, include it in your source code files, or store it in somewhere accessible from your SilverStripe backend via HTTP(s).

Below is an example to embed the mapping file in YAML config file:

Based on your need, there are 3 provider class ready to use: JSONStringProvider, HTTPProvider, and FileProvider. You may also roll your own and inject it as the PersistedQueryMappingProvider if needed.

Step 4: This step is actually achieved by silverstripe/silverstripe-graphql module, and you can just rely on it. Unfortunately, the pull request to the official repository has not been merged yet, it is welcome to use my fork of this repo at this moment. To do this, you need to modify your composer.json file:

"repositories": [
...
{
"type": "vcs",
"url": "https://github.com/zzdjk6/silverstripe-graphql"
}
],
"require": {
...
"silverstripe/recipe-cms": "4.2.x-dev",
"silverstripe/recipe-core": "4.2.x-dev",
"silverstripe/graphql": "2.0.x-dev"
},
"prefer-stable": true,
"minimum-stability": "dev"

Finally, let’s have a glance of the result:

Request
Response

One more thing, if you want to find an open-source example that actually use this feature, feel free to checkout https://github.com/zzdjk6/Groot. It is one of my side projects, and it is intended to be a self-hosted music platform (roll your own Spotify, yeah :)

Passionate JS/TS Developer with a Fullstack Background