website/src/docs/hotchocolate/v16/performance/automatic-persisted-operations.md
This guide walks you through how automatic persisted operations work and how to set them up with a Hot Chocolate GraphQL server.
The Automatic Persisted Queries (APQ) protocol was originally specified by Apollo and represents an evolution of the persisted operations feature that many GraphQL servers implement. Instead of storing operation documents ahead of time, the client stores operation documents dynamically. This preserves the performance benefits of persisted operations but removes the friction of setting up build processes that post-process client application source code.
When the client makes a request to the server, it optimistically sends a short cryptographic hash instead of the full operation document.
Hot Chocolate inspects the incoming request for an operation ID or a full GraphQL operation document. If the request has only an operation ID, the execution engine tries to resolve the full operation document from the operation document storage. If the storage contains an operation document that matches the provided operation ID, the request is upgraded to a fully valid GraphQL request and executed.
If the operation document storage does not contain an operation document that matches the sent operation ID, Hot Chocolate returns an error result indicating the operation document was not found (this only happens the first time a client asks for a certain operation document). The client application then sends a second request with the specified operation document ID and the complete GraphQL operation document. This triggers Hot Chocolate to store the new operation document in its operation document storage and execute the operation, returning the result.
The following tutorial walks you through creating a Hot Chocolate GraphQL server configured to support automatic persisted operations.
Open your preferred terminal and select a directory for this tutorial.
dotnet new install HotChocolate.Templates
dotnet new graphql
Configure your GraphQL server to handle automatic persisted operation requests. Register the in-memory operation storage and configure the automatic persisted operation request pipeline.
builder
.AddGraphQL()
.AddQueryType<Query>()
.UseAutomaticPersistedOperationPipeline();
builder
.AddGraphQL()
.AddQueryType<Query>()
.UseAutomaticPersistedOperationPipeline()
.AddInMemoryOperationDocumentStorage();
builder.Services.AddMemoryCache();
builder
.AddGraphQL()
.AddQueryType<Query>()
.UseAutomaticPersistedOperationPipeline()
.AddInMemoryOperationDocumentStorage();
Now that your server is set up with automatic persisted operations, verify that it works as expected. You can do this using your console and curl. For this example, you will use a dummy operation {__typename} with an MD5 hash serialized to base64 as an operation ID 71yeex4k3iYWQgg9TilDIg==. The following steps walk you through the full automatic persisted operation flow.
dotnet run
Request
curl -g 'http://localhost:5000/graphql/?extensions={"persistedQuery":{"version":1,"md5Hash":"71yeex4k3iYWQgg9TilDIg=="}}'
Response
The response indicates, as expected, that this operation is unknown.
{
"errors": [
{
"message": "PersistedQueryNotFound",
"extensions": { "code": "HC0020" }
}
]
}
query parameter with the full GraphQL operation string.Request
curl -g 'http://localhost:5000/graphql/?query={__typename}&extensions={"persistedQuery":{"version":1,"md5Hash":"71yeex4k3iYWQgg9TilDIg=="}}'
Response
The GraphQL server responds with the operation result and indicates that the operation was stored on the server ("persisted": true).
{
"data": { "__typename": "Query" },
"extensions": {
"persistedQuery": {
"md5Hash": "71yeex4k3iYWQgg9TilDIg==",
"persisted": true
}
}
}
Request
curl -g 'http://localhost:5000/graphql/?extensions={"persistedQuery":{"version":1,"md5Hash":"71yeex4k3iYWQgg9TilDIg=="}}'
Response
This time the server knows the operation and responds with the result.
{ "data": { "__typename": "Query" } }
In this example, you used GraphQL HTTP GET requests, which are also useful in caching scenarios with CDNs. The automatic persisted operation flow also works with GraphQL HTTP POST requests.
Hot Chocolate is configured to use the MD5 hashing algorithm by default, serialized to a base64 string. Hot Chocolate ships with support for MD5, SHA1, and SHA256 and can serialize the hash to base64 or hex. The following steps walk you through changing the hashing algorithm to SHA256 with hex serialization.
builder.Services.AddMemoryCache();
builder
.AddGraphQL()
.AddSha256DocumentHashProvider(HashFormat.Hex)
.AddQueryType<Query>()
.UseAutomaticPersistedOperationPipeline()
.AddInMemoryOperationDocumentStorage();
dotnet run
7f56e67dd21ab3f30d1ff8b7bed08893f0a0db86449836189b361dd1e56ddb4b.Request
curl -g 'http://localhost:5000/graphql/?query={__typename}&extensions={"persistedQuery":{"version":1,"sha256Hash":"7f56e67dd21ab3f30d1ff8b7bed08893f0a0db86449836189b361dd1e56ddb4b"}}'
Response
{
"data": { "__typename": "Query" },
"extensions": {
"persistedQuery": {
"sha256Hash": "7f56e67dd21ab3f30d1ff8b7bed08893f0a0db86449836189b361dd1e56ddb4b",
"persisted": true
}
}
}
If you run multiple Hot Chocolate server instances and want to preserve stored operation documents after a server restart, use a persisted operation document storage. Hot Chocolate supports a file-system-based operation document storage, Azure Blob Storage, or a Redis cache.
docker run --name redis-stitching -p 7000:6379 -d redis
builder.Services.AddMemoryCache();
builder
.AddGraphQL()
.AddSha256DocumentHashProvider(HashFormat.Hex)
.AddQueryType<Query>()
.UseAutomaticPersistedOperationPipeline()
.AddRedisOperationDocumentStorage(services =>
ConnectionMultiplexer.Connect("localhost:7000").GetDatabase());
dotnet run
Request
curl -g 'http://localhost:5000/graphql/?query={__typename}&extensions={"persistedQuery":{"version":1,"sha256Hash":"7f56e67dd21ab3f30d1ff8b7bed08893f0a0db86449836189b361dd1e56ddb4b"}}'
Response
{
"data": { "__typename": "Query" },
"extensions": {
"persistedQuery": {
"sha256Hash": "7f56e67dd21ab3f30d1ff8b7bed08893f0a0db86449836189b361dd1e56ddb4b",
"persisted": true
}
}
}
Stop your GraphQL server.
Start your GraphQL server again.
dotnet run
Request
curl -g 'http://localhost:5000/graphql/?extensions={"persistedQuery":{"version":1,"sha256Hash":"7f56e67dd21ab3f30d1ff8b7bed08893f0a0db86449836189b361dd1e56ddb4b"}}'
Response
{ "data": { "__typename": "Query" } }