Persisted GraphQL queries persisted-graphql-queries
Persisted queries are GraphQL queries that are created and stored on the Adobe Experience Manager (AEM) as a Cloud Service server. They can be requested with a GET request by client applications. The response of a GET request can be cached at the dispatcher and CDN layers, ultimately improving the performance of the requesting client application. This differs from standard GraphQL queries, which are executed using POST requests where the response cannot easily be cached.
The GraphiQL IDE is available in AEM for you to develop, test, and persist your GraphQL queries, before transferring to your production environment. For cases that need customization (for example, when customizing the cache) you can use the API; see the cURL example provided in How to persist a GraphQL query.
Persisted Queries and Endpoints persisted-queries-and-endpoints
Persisted queries must always use the endpoint related to the appropriate Sites configuration; so they can use either, or both:
- The Global configuration and endpoint
The query has access to all Content Fragment Models. - Specific Sites configuration(s) and endpoint(s)
Creating a persisted query for a specific Sites configuration requires a corresponding Sites-configuration-specific endpoint (to provide access to the related Content Fragment Models).
For example, to create a persisted query specifically for the WKND Sites configuration, a corresponding WKND-specific Sites configuration, and a WKND-specific endpoint must be created in advance.
For example, if there is a particular query called my-query
, which uses a model my-model
from the Sites configuration my-conf
:
- You can create a query using the
my-conf
specific endpoint, and then the query is saved as following:/conf/my-conf/settings/graphql/persistentQueries/my-query
- You can create the same query using
global
endpoint, but then the query is saved as following:/conf/global/settings/graphql/persistentQueries/my-query
How to persist a GraphQL query how-to-persist-query
It is recommended to persist queries on an AEM author environment initially and then transfer the query to your production AEM publish environment, for use by applications.
There are various methods of persisting queries, including:
- GraphiQL IDE - see Saving Persisted Queries (preferred method)
- cURL - see the following example
- Other tools, including Postman
The GraphiQL IDE is the preferred method for persisting queries. To persist a given query using the cURL command line tool:
-
Prepare the query by PUTing it to the new endpoint URL
/graphql/persist.json/<config>/<persisted-label>
.For example, create a persisted query:
code language-shell $ curl -X PUT \ -H 'authorization: Basic YWRtaW46YWRtaW4=' \ -H "Content-Type: application/json" \ "http://localhost:4502/graphql/persist.json/wknd/plain-article-query" \ -d \ '{ articleList { items{ _path author main { json } } } }'
-
At this point, check the response.
For example, check for success:
code language-json { "action": "create", "configurationName": "wknd", "name": "plain-article-query", "shortPath": "/wknd/plain-article-query", "path": "/conf/wknd/settings/graphql/persistentQueries/plain-article-query" }
-
You can then request the persisted query by GETing the URL
/graphql/execute.json/<shortPath>
.For example, use the persisted query:
code language-shell $ curl -X GET \ http://localhost:4502/graphql/execute.json/wknd/plain-article-query
-
Update a persisted query by POSTing to an already existing query path.
For example, use the persisted query:
code language-shell $ curl -X POST \ -H 'authorization: Basic YWRtaW46YWRtaW4=' \ -H "Content-Type: application/json" \ "http://localhost:4502/graphql/persist.json/wknd/plain-article-query" \ -d \ '{ articleList { items{ _path author main { json } referencearticle { _path } } } }'
-
Create a wrapped plain query.
For example:
code language-shell $ curl -X PUT \ -H 'authorization: Basic YWRtaW46YWRtaW4=' \ -H "Content-Type: application/json" \ "http://localhost:4502/graphql/persist.json/wknd/plain-article-query-wrapped" \ -d \ '{ "query": "{articleList { items { _path author main { json } referencearticle { _path } } } }"}'
-
Create a wrapped plain query with cache control.
For example:
code language-shell $ curl -X PUT \ -H 'authorization: Basic YWRtaW46YWRtaW4=' \ -H "Content-Type: application/json" \ "http://localhost:4502/graphql/persist.json/wknd/plain-article-query-max-age" \ -d \ '{ "query": "{articleList { items { _path author main { json } referencearticle { _path } } } }", "cache-control": { "max-age": 300 }}'
-
Create a persisted query with parameters:
For example:
code language-shell $ curl -X PUT \ -H 'authorization: Basic YWRtaW46YWRtaW4=' \ -H "Content-Type: application/json" \ "http://localhost:4502/graphql/persist.json/wknd/plain-article-query-parameters" \ -d \ 'query GetAsGraphqlModelTestByPath($apath: String!, $withReference: Boolean = true) { articleByPath(_path: $apath) { item { _path author main { plaintext } referencearticle @include(if: $withReference) { _path } } } }'
How to execute a Persisted query execute-persisted-query
To execute a Persisted query, a client application makes a GET request using the following syntax:
GET <AEM_HOST>/graphql/execute.json/<PERSISTENT_PATH>
Where PERSISTENT_PATH
is a shortened path to where the Persisted query is saved.
-
For example,
wknd
is the configuration name andplain-article-query
is the name of the Persisted query. To execute the query:code language-shell $ curl -X GET \ https://publish-p123-e456.adobeaemcloud.com/graphql/execute.json/wknd/plain-article-query
-
Executing a query with parameters.
note note NOTE Query variables and values must be properly encoded when executing a Persisted query. For example:
code language-xml $ curl -X GET \ "https://publish-p123-e456.adobeaemcloud.com/graphql/execute.json/wknd/plain-article-query-parameters%3Bapath%3D%2Fcontent%2Fdam%2Fwknd%2Fen%2Fmagazine%2Falaska-adventure%2Falaskan-adventures%3BwithReference%3Dfalse
See using query variables for more details.
Using query variables query-variables
Query variables can be used with Persisted Queries. The query variables are appended to the request prefixed with a semicolon (;
) using the variable name and value. Multiple variables are separated by semicolons.
The pattern looks like the following:
<AEM_HOST>/graphql/execute.json/<PERSISTENT_QUERY_PATH>;variable1=value1;variable2=value2
For example, the following query contains a variable activity
to filter a list based on an activity value:
query getAdventuresByActivity($activity: String!) {
adventureList (filter: {
adventureActivity: {
_expressions: [
{
value: $activity
}
]
}
}){
items {
_path
adventureTitle
adventurePrice
adventureTripLength
}
}
}
This query can be persisted under a path wknd/adventures-by-activity
. To call the Persisted query where activity=Camping
the request would look like this:
<AEM_HOST>/graphql/execute.json/wknd/adventures-by-activity%3Bactivity%3DCamping
The UTF-8 encoding %3B
is for ;
and %3D
is the encoding for =
. The query variables and any special characters must be encoded properly for the Persisted query to execute.
Using query variables - Best Practices query-variables-best-practices
When using variables in your queries there are a few best practices that should be followed:
-
Encoding
As a general approach, it is always recommended to encode all special characters; for example,;
,=
,?
,&
, among others. -
Semicolon
Persisted queries that use multiple variables (that are separated by semicolons) need to have either:- the semicolons encoded (
%3B
); encoding the URL will also achieve this - or a trailing semicolon added to the end of the query
- the semicolons encoded (
-
CACHE_GRAPHQL_PERSISTED_QUERIES
WhenCACHE_GRAPHQL_PERSISTED_QUERIES
is enabled for the Dispatcher, then parameters that contain the/
or\
characters in their value, are encoded twice at the Dispatcher level.
To avoid this situation:-
Enable
DispatcherNoCanonURL
on the Dispatcher.
This will instruct the Dispatcher to forward the original URL to AEM, so preventing duplicated encodings.
However this setting currently only works on thevhost
level, so if you already have Dispatcher configurations to rewrite URLs (e.g. when using shortened URLs) you might need a separatevhost
for persisted query URLs. -
Send
/
or\
characters unencoded.
When calling the persisted query URL ensure that all/
or\
characters remain unencoded in the value of persisted query variables.note note NOTE This option is only recommended for when the DispatcherNoCanonURL
solution cannot be implemented for any reason.
-
-
CACHE_GRAPHQL_PERSISTED_QUERIES
When
CACHE_GRAPHQL_PERSISTED_QUERIES
is enabled for the Dispatcher, then the;
character cannot be used in the value of a variable.
Caching your persisted queries caching-persisted-queries
Persisted queries are recommended as they can be cached at the Dispatcher and Content Delivery Network (CDN) layers, ultimately improving the performance of the requesting client application.
By default AEM will invalidate cache based on a Time To Live (TTL) definition. These TTLs can be defined by the following parameters. These parameters can be accessed by various means, with variations in the names according to the mechanism used:
max-age
cache-control : max-age
cacheControlMaxAge
graphqlCacheControl
s-maxage
surrogate-control : max-age
surrogateControlMaxAge
graphqlSurrogateControl
stale-while-revalidate
surrogate-control : stale-while-revalidate
surrogateControlStaleWhileRevalidate
graphqlStaleWhileRevalidate
stale-if-error
surrogate-control : stale-if-error
surrogateControlStaleIfError
graphqlStaleIfError
Author instances author-instances
For author instances the default values are:
max-age
: 60s-maxage
: 60stale-while-revalidate
: 86400stale-if-error
: 86400
These:
-
cannot be overwritten:
- with an OSGi configuration
-
can be overwritten:
- by a request that defines HTTP header settings using cURL; it should include suitable settings for
cache-control
and/orsurrogate-control
; for examples, see Managing Cache at the Persisted Query Level - if you specify values in the Headers dialog of the GraphiQL IDE
- by a request that defines HTTP header settings using cURL; it should include suitable settings for
Publish instances publish-instances
For publish instances the default values are:
max-age
: 60s-maxage
: 7200stale-while-revalidate
: 86400stale-if-error
: 86400
These can be overwritten:
-
at the Persisted Query Level; this involves posting the query to AEM using cURL in your command line interface, and publishing the Persisted Query.
Managing HTTP Cache Headers in the GraphiQL IDE http-cache-headers-graphiql-ide
The GraphiQL IDE - see Saving Persisted Queries
Managing Cache at the Persisted Query Level cache-persisted-query-level
This involves posting the query to AEM using cURL in your command line interface.
For an example of the PUT (create) method:
curl -u admin:admin -X PUT \
--url "http://localhost:4502/graphql/persist.json/wknd/plain-article-query-max-age" \
--header "Content-Type: application/json" \
--data '{ "query": "{articleList { items { _path author } } }", "cache-control": { "max-age": 300 }, "surrogate-control": {"max-age":600, "stale-while-revalidate":1000, "stale-if-error":1000} }'
For an example of the POST (update) method:
curl -u admin:admin -X POST \
--url "http://localhost:4502/graphql/persist.json/wknd/plain-article-query-max-age" \
--header "Content-Type: application/json" \
--data '{ "query": "{articleList { items { _path author } } }", "cache-control": { "max-age": 300 }, "surrogate-control": {"max-age":600, "stale-while-revalidate":1000, "stale-if-error":1000} }'
The cache-control
can be set at the creation time (PUT) or later on (for example, via a POST request for instance). The cache-control is optional when creating the persisted query, as AEM can provide the default value. See How to persist a GraphQL query, for an example of persisting a query using cURL.
Managing Cache with Cloud Manager variables cache-cloud-manager-variables
Cloud Manager Environment Variables can be defined with Cloud Manager to define the required values:
graphqlStaleIfError
graphqlSurrogateControl
Managing Cache with an OSGi configuration cache-osgi-configration
To manage the cache globally, you can configure the OSGi settings for the Persisted Query Service Configuration.
The default OSGi configuration for publish instances:
-
reads the Cloud Manager variables if available:
table 0-row-3 1-row-3 2-row-3 3-row-3 4-row-3 layout-auto OSGi Configuration Property reads this Cloud Manager Variable cacheControlMaxAge
reads graphqlCacheControl
surrogateControlMaxAge
reads graphqlSurrogateControl
surrogateControlStaleWhileRevalidate
reads graphqlStaleWhileRevalidate
surrogateControlStaleIfError
reads graphqlStaleIfError
-
and if not available, the OSGi configuration uses the default values for publish instances.
Configuring the query response code configuring-query-response-code
By default the PersistedQueryServlet
sends a 200
response when it executes a query, regardless of the actual result.
You can configure the OSGi settings for the Persisted Query Service Configuration to control whether more detailed status codes are returned by the /execute.json/persisted-query
endpoint, when there is an error in the persisted query.
The field Respond with application/graphql-response+json
(responseContentTypeGraphQLResponseJson
) can be defined as required:
-
false
(default value):
It does not matter whether the persisted query is successful or not. TheContent-Type
header returned isapplication/json
, and the/execute.json/persisted-query
always returns the status code200
. -
true
:
The returnedContent-Type
isapplication/graphql-response+json
, and the endpoint will return the appropriate response code when there is any form of error upon running the persisted query:table 0-row-2 1-row-2 2-row-2 3-row-2 4-row-2 Code Description 200 Successful response 400 Indicates that there are missing headers, or an issue with the persisted query path. For example, configuration name not specified, suffix is not specified, and others.
See Troubleshooting - GraphQL endpoint not configured.404 The requested resource cannot be found. For example, the Graphql endpoint is not available on the server.
See Troubleshooting - Missing path in the GraphQL persisted query URL.500 Internal server error. For example, validation errors, persistence error, and others. note note NOTE See also https://graphql.github.io/graphql-over-http/draft/#sec-Status-Codes
Encoding the query URL for use by an app encoding-query-url
For use by an application, any special characters used when constructing query variables (that is, semicolons (;
), equal sign (=
), slashes /
) must be converted to use the corresponding UTF-8 encoding.
For example:
curl -X GET \ "https://publish-p123-e456.adobeaemcloud.com/graphql/execute.json/wknd/adventure-by-path%3BadventurePath%3D%2Fcontent%2Fdam%2Fwknd%2Fen%2Fadventures%2Fbali-surf-camp%2Fbali-surf-camp"
The URL can be broken down into the following parts:
/graphql/execute.json
/wknd/adventure-by-path
%3B
;
adventurePath
%3D
=
%2F
/
%2Fcontent%2Fdam...
In plain text the request URI looks like the following:
/graphql/execute.json/wknd/adventure-by-path;adventurePath=/content/dam/wknd/en/adventures/bali-surf-camp/bali-surf-camp
To use a persisted query in a client app, the AEM headless client SDK should be used for JavaScript, Java, or NodeJS. The Headless Client SDK will automatically encode any query variables appropriately in the request.
Transferring a persisted query to your Production environment transfer-persisted-query-production
Persisted queries should always be created on an AEM Author service and then published (replicated) to an AEM Publish service. Often, Persisted queries are created and tested on lower environments like local or Development environments. It is then necessary to promote Persisted queries to higher level environments, ultimately making them available on a production AEM Publish environment for client applications to consume.
Package Persisted queries
Persisted queries can be built into AEM Packages. AEM Packages can then be downloaded and installed on different environments. AEM Packages can also be replicated from an AEM Author environment to AEM Publish environments.
To create a Package:
- Navigate to Tools > Deployment > Packages.
- Create a new package by tapping Create Package. This opens a dialog to define the Package.
- In the Package Definition Dialog, under General enter a Name like “wknd-persistent-queries”.
- Enter a version number like “1.0”.
- Under Filters add a new Filter. Use the Path Finder to select the
persistentQueries
folder beneath the configuration. For example, for thewknd
configuration, the full path is/conf/wknd/settings/graphql/persistentQueries
. - Select Save to save the new Package definition and close the dialog.
- Select the Build button in the created Package definition.
After the package has been built you can:
- Download the package and re-upload on a different environment.
- Replicate the package by tapping More > Replicate. This will replicate the package to the connected AEM Publish environment.