GraphQL Dynamic Persisted Queries
In my previous post GraphQL Persisted Queries Using GET Requests we built a GraphQL server that used middleware to pattern match against a static extracted queries file. That extracted queries file housed the query we knew our client would send. As you can imagine, keeping a static file in sync between the server and client can be a pain, this approach just won’t scale. To solve this problem we’ll use a technique called Dynamic Persisted Queries. We will build a react client that dynamically generates a hash and sends that over to our server. We’ll also update our GraphQL server to not only execute the corresponding query, but save the hash in memory for all subsequent requests.
If you haven’t read my previous post I suggest you start there, as this tutorial will pick up right where we left off. For those of you that are up to date with the idea of persisted queries and want to skip the previous article, you can find the project files here: source files.
npm install -g create-react-app redis-cli
brew install redis
npm install ioredis cors --save
We require the
cors middleware on line 9 and add it to our
/graphql route on line 17. We should also update our schemas and resolvers to give us a bit more data to play with.
npm install --save graphql graphql-tag react-apollo apollo-client apollo-link-http apollo-cache-inmemory apollo-link-persisted-queries react-router react-router-dom
Lets create a queries file with a couple queries for our react client to create hashes for.
Here we add two queries to our queries.js file using graphql-tag,
extendGreeting query simply adds a few more fields to the response. We’ll need to create a component to call our home page and two additional components that’ll query our GraphQL server as we navigate from page to page.
home.js component simply returns a string informing us of our location. The
pageTwo.js components both use the
Query component from react-apollo to send requests to our GraphQL server. Inside our
Query components we pass in the function to be run once we receive a response. If the request is pending we’ll show the text
Loading... and if an error occurs we’ll render the error message. If all goes well, we’ll use the data received to display our greeting. To finish up our client we’ll use some very cool packages from Apollo to tie it all together. Let’s open up
index.js and replace it with the following.
We begin by instantiating a new client using the
apolloClient class from apollo-client. We also need to pass in the required configuration options. We’ll use the
InMemoryCache function from apollo-cache-inmemory for the value of our cache property (A required step when using Apollo Client 2.0). Next, for the link property we’ll use the
createPersistedQueryLink link from apollo-link-persisted-queries. We pass in the
useGETForHashedQueries option and set it to
true, this is to ensure we don’t lose our ability to cache on the edge. Next, we’ll concat the
createHTTPLink link from apollo-http-link and pass in our GraphQL server endpoint.
To render our app we’ll pass in the
ApolloProvider component from react-apollo as an argument to the
render function. We pass the client we created above as a prop to
ApolloProvider , then use the
BrowserRouter component from react-router as a child to configure our routes. Finally we’re ready to fire this bad boy up and display some content in the browser. Open a terminal window in the root directory of both the GraphQL server and our react client, and run the following commands.
npm run start
In the GraphQL server terminal you should see
listening on port: 4000 . In the terminal for the react client you should see
You can now view frontend in the browser. Open up your browser and navigate to
Ok, here’s where the magic happens. Despite the simplicity of the web page we’ve constructed, as you navigate from page to page you will notice something very cool happening. Our react client is now sending an auto generated hash to our GraphQL server. We have yet to update our
persistedQueries.js file but our GraphQL queries are still succeeding. This is due to
apollo-link-persisted-queries being smart enough to detect the error codes our GraphQL server is sending back. In this case our GraphQL server is responding with a
400 Bad Request, which our react client recognizes as a total failure and retries the query using a
post request (the default approach). Since all changes we’ve made so far are backwards compatible the second request succeeds allowing our pages to render successfully.
There is a lot going on here so lets break this down step by step. First our
persistedQueries middleware calls the
method function passing in the
method function detects whether we are receiving a
post request or a
get request. If our GraphQL server received a
get request then the data we’re looking for arrived as
JSON and resides in the
query property. If our server received a
Next, we destructure the
data object in search of the
sha256Hash. If the hash doesn’t exists we’ll call
next() and send the request on it’s way to the next middleware function(s). If the hash exists we’ll check the status of our redis client. If we were unable to establish a connection to our redis client then we call
handleQuery, which will go ahead and process the request without searching for the
sha256Hash in our redis database. If our redis connection was successful, then we’ll build our cache key by appending a namespace to the operation name and the
sha256Hash. Then we use that cache key to query our redis db. Whether we find a match or not, we’ll call
handleQuery to process the rest of the request.
By this time, if the cache key we constructed corresponds to a GraphQL query saved in our db, we’ll go ahead and assign that to the
query property on the
request object. Then we call
next, sending the requested query to be processed by our GraphQL server.
If the cache key never returns a GraphQL query from our redis db, and the request data does not contain a query property (as it does when it’s a
post request), then our GraphQL server will send back an error response with a very important message,
persistedQueryNotFound. This exact error message is used to inform our
apollo-link-persisted-queries package to retry the request using a standard
post request. Only this time, the retry will also include the original
sha256Hash it sent with the first attempt.
If the request does in fact contain a query property, then we’ll use GraphQL’s
validate method to ensure its value is a valid GraphQL query.
validate returns an array of all errors found, in our case we check the length of the array and send back an error message if any errors were encountered.
Once we’re confident that our query is valid, we can check the status of our redis client connection. If a connection hasn’t been established we’ll go ahead and just send the query to our GraphQL server. If our redis connection was successful however, we’ll build our cache key and save the query in our db. Now all subsequent requests containing the same
sha256Hash will be processed successfully. Time to see all of our hard work payoff. Let’s spin up our redis db.
Then restart both apps in their respective terminals and open up your browser.
On the first visit to
page one or
page two our react client sends a generated hash to our GraphQL server requesting the data it needs. This request, of course, fails since the generated hash is not yet saved in our redis database. Our GraphQL server responds with the proper error message
persistedQueryNotFound, prompting our react client to retry the request once again as a
post request while including the
sha256Hash. The retry is successfully fulfilled and the
sha256Hash is saved in our redis db. From this point on, all subsequent requests are fulfilled using only a
Success! Now we can remove our
extracted_queries.js file, eliminating the burden of keeping a static file in sync between the server and the client. In case you had any trouble following along, you can find the completed project files here: source files.