Persisted Queries
Secure your graph while minimizing request latency
Apollo supports two separate but related features called automatic persisted queries (APQs) and persisted queries. With both features, clients can execute a GraphQL operation by sending an operation's ID instead of the entire operation string. An operation's ID is a hash of the full operation string. Querying by ID can significantly reduce latency and bandwidth usage for large operation strings.
Differences between persisted queries and APQ
The persisted queries feature requires operations to be registered in a persisted query list (PQL). This allows the PQL to act as an operation safelist made by your first-party apps. As such, persisted queries is a security feature as much as a performance one.
With APQs, if the server can't find the operation ID the client provides, the server returns an error indicating that it needs the full operation string. If an Apollo client receives this error, it automatically retries the operation with the full operation string.
If you only want to improve request latency and bandwidth usage, APQ addresses your use case. If you also want to secure your supergraph with operation safelisting, you should register operations in a PQL.
For more details on differences between persisted queries and APQ, see the GraphOS persisted queries documentation.
Implementation steps
Both persisted queries and APQs require you to configure code generation and how your client makes requests. If you intend to use persisted queries for safelisting, you also need to generate an operations manifest.
We recommend you follow this order while implementing:
Implementation Step | Required for PQs? | Required for APQs? |
---|---|---|
1. Configure generated operation models | ✅ | ✅ |
2. Generate the operation manifest | ✅ | -- |
3. Publish the operation manifest | ✅ | -- |
4. Enable persisted queries on the client when it makes requests | ✅ | ✅ |
The rest of this article details these steps.
Persisted queries also require you to create and link a PQL, and to configure your router to receive persisted query requests. This document only describes the steps that need to be taken by the client to create a manifest of the client's operations and send persisted query requests. For more information on the other configuration aspects of persisted queries, see the GraphOS persisted queries documentation.
0. Requirements
You can use APQ with the following versions of Apollo iOS, Apollo Server, and Apollo Router Core:
Apollo iOS (v1.0.0+)
Apollo Server (v1.0.0+)
Apollo Router Core (v0.1.0+)
Note: You can use either Apollo Server or Apollo Router Core for APQs. They don't need to be used together.
Using persisted queries for safelisting has the following requirements:
Apollo iOS (v1.4.0+)
GraphOS Router (v1.25.0+)
1. Configure generated operation models
Both persisted queries and APQs require your code generation to include operation IDs. You can configure this in your code generation configuration's options
.
Specifically, set the operationDocumentFormat
array to definition
, operationId
, or both definition
and operationId
.
To use APQs, you must include both the
definition
andoperationId
.For persisted queries, you only need the
operationId
.
1"options": {
2 "operationDocumentFormat" : [
3 "definition",
4 "operationId"
5 ]
6}
2. Generate operation manifest
This step is only required for implementing safelisting with persisted queries. It is not required for APQs.
An operation manifest acts as a safelist of trusted operations the GraphOS Router can check incoming requests against. You can generate operation manifests by adding the operationManifest
option to your ApolloCodegenConfiguration
JSON file like so:
1"operationManifest" : {
2 "generateManifestOnCodeGeneration" : false,
3 "path" : "/operation/identifiers/path",
4 "version" : "persistedQueries"
5}
Once these options are configured you can run the generate-operation-manifest
in order to generate your operation manifest. If you have the generateManifestOnCodeGeneration
flag set to true
your operation manifest will also generate everytime you run the generate
command.
The resulting operation manifest for persistedQueries
looks like this:
1{
2 "format": "apollo-persisted-query-manifest",
3 "version": 1,
4 "operations": [
5 {
6 "id": "e0321f6b438bb42c022f633d38c19549dea9a2d55c908f64c5c6cb8403442fef",
7 "body": "query GetItem { thing { __typename } }",
8 "name": "GetItem",
9 "type": "query"
10 }
11 ]
12}
To automatically update the manifest for each new app release, you can include the generate
or generate-operation-manifest
command in your CI/CD pipeline.
3. Publish operation manifest
This step is only required for implementing safelisting with persisted queries. It is not required for APQs.
0.17.2
or later. Previous versions of Rover don't support publishing operations to a PQL.
Download the latest version.After you generate an operation manifest, you publish it to your PQL with the Rover CLI like so:
rover persisted-queries publish my-graph@my-variant \
--manifest ./persisted-query-manifest.json
The
my-graph@my-variant
argument is thegraph ref
of any variant the PQL is linked to.Graph refs have the format
graph-id@variant-name
.
Use the
--manifest
option to provide the path to the manifest you want to publish.
persisted-queries publish
command assumes manifests are in the format generated by Apollo client tools. The command can also support manifests generated by the Relay compiler by adding the --manifest-format relay
argument. Your Rover CLI version must be 0.19.0 or later to use this argument.The persisted-queries publish
command does the following:
Publishes all operations in the provided manifest file to the PQL linked to the specified variant, or to the specified PQL.
Publishing a manifest to a PQL is additive. Any existing entries in the PQL remain.
If you publish an operation with the same
id
but different details from an existing entry in the PQL, the entire publish command fails with an error.
Updates any other variants that the PQL is applied to so that routers associated with those variants can fetch their updated PQL.
As with generating manifests, it's best to execute this command in your CI/CD pipeline to publish new operations as part of your app release process. The API key you supply to Rover must have the role of Graph Admin or Persisted Query Publisher. Persisted Query Publisher is a special role designed for use with the rover persisted-queries publish
command; API keys with this role have no other access to your graph's data in GraphOS, and are appropriate for sharing with trusted third party client developers who should be allowed to publish operations to your graph's PQL but should not otherwise have access to your graph.
Test operations
You can send some test operations to test that you've successfully published your manifests:
First, start your GraphOS-connected router:
APOLLO_KEY="..." APOLLO_GRAPH_REF="..." ./router --config ./router.yaml
2023-05-11T15:32:30.684460Z INFO Apollo Router v1.18.1 // (c) Apollo Graph, Inc. // Licensed as ELv2 (https://go.apollo.dev/elv2)
2023-05-11T15:32:30.684480Z INFO Anonymous usage data is gathered to inform Apollo product development. See https://go.apollo.dev/o/privacy for details.
2023-05-11T15:32:31.507085Z INFO Health check endpoint exposed at http://127.0.0.1:8088/health
2023-05-11T15:32:31.507823Z INFO GraphQL endpoint exposed at http://127.0.0.1:4000/ 🚀
Next, make a POST request with curl
, like so:
curl http://localhost:4000 -X POST --json \
'{"extensions":{"persistedQuery":{"version":1,"sha256Hash":"dc67510fb4289672bea757e862d6b00e83db5d3cbbcfb15260601b6f29bb2b8f"}}}'
If your router's PQL includes an operation with an ID that matches the value of the provided sha256Hash
property, it executes the corresponding operation and returns its result.
4. Enable persisted queries on ApolloClient
Once you've configured your code generation to include operation IDs, you can update your client to query by operation ID rather than the full operation string. This configuration is the same whether you're using APQ or persisted queries:
Initialize a custom
NetworkTransport
usingRequestChainNetworkTransport
withautoPersistQueries
parameter set totrue
and aninterceptorProvider
that includes theAutomaticPersistedQueryInterceptor
(such asDefaultInterceptorProvider
).Initialize your
ApolloClient
with the customNetworkTransport
that supports persisted queries.
1let store = ApolloStore(cache: InMemoryNormalizedCache())
2let interceptorProvider = DefaultInterceptorProvider(store: store)
3let networkTransport = RequestChainNetworkTransport(
4 interceptorProvider: interceptorProvider,
5 endpointURL: URL(string: "http://localhost:4000/graphql")!,
6 autoPersistQueries: true
7)
8
9let client = ApolloClient(networkTransport: networkTransport, store: store)
For more information on configuring your ApolloClient
, NetworkTransport
, InterceptorProvider
, and the request chain, see the request pipeline documentation.
Sending APQ retries as GET
requests
Note: Apollo iOS only retries failed ID-based operations for APQs, not persisted queries.
By default, the Apollo clients sends operation retries as POST
requests. In some cases, you may prefer or need to retry an operation using a GET
request: for example, you may make requests to a CDN that has better performance with GET
s.
To use GET
for APQ retry requests, set the useGETForPersistedQueryRetry
on your RequestChainNetworkTransport
to true
.
In most cases, keeping the default option (false
) suffices.