Persisted Operations
Persisted operations is a mechanism for preventing the execution of arbitrary GraphQL operation documents.
By default, the persisted operations plugin follows the the APQ Specification of Apollo for SENDING hashes to the server.
However, you can change this behavior by overriding the getPersistedOperationKey
option to support Relay's specification for example.
Installation
yarn add @graphql-yoga/persisted-operations
Quick Start
import { createYoga } from 'graphql-yoga'
import { createServer } from 'node:http'
import { usePersistedOperations } from '@graphql-yoga/plugin-persisted-operations'
const store = {
ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38:
'{__typename}'
}
const yoga = createYoga({
plugins: [
usePersistedOperations({
getPersistedOperation(sha256Hash: string) {
return store[sha256Hash]
}
})
]
})
const server = createServer(yoga)
server.listen(4000, () => {
console.info('Server is running on http://localhost:4000/graphql')
})
Start your yoga server and send the following request.
curl -X POST -H 'Content-Type: application/json' http://localhost:4000/graphql \
-d '{"extensions":{"persistedQuery":{"version":1,"sha256Hash":"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38"}}}'
Then afterwards we can send the same payload again, but this time omit the query
field.
Especially for big GraphQL document strings, the subsequent payload can be much smaller.
Extracting client operations
You can use GraphQL Code Generator with the graphql-codegen-persisted-query-ids
plugin for extracting a map of persisted query ids and their corresponding GraphQL documents from your client-code in a JSON file.
Example Map
{
"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38": "{__typename}",
"c7a30a69b731d1af42a4ba02f2fa7a5771b6c44dcafb7c3e5fa4232c012bf5e7": "mutation {__typename}"
}
This map can then be used to persist the GraphQL documents in the server.
import { createYoga } from 'graphql-yoga'
import { createServer } from 'node:http'
import { usePersistedOperations } from '@graphql-yoga/plugin-persisted-operations'
import persistedOperations from './persistedOperations.json'
const yoga = createYoga({
plugins: [
usePersistedOperations({
getPersistedOperation(key: string) {
return persistedOperations[key]
}
})
]
})
const server = createServer(yoga)
server.listen(4000, () => {
console.info('Server is running on http://localhost:4000/graphql')
})
Sending the hash from the client
The persisted operations plugin follows the the APQ Specification of Apollo for SENDING hashes to the server.
GraphQL clients such Apollo Client
and Urql
support that out of the box.
Check the corresponding documentation for more information.
Allowing arbitrary GraphQL operations
Sometimes it is handy to allow non-persisted operations aside from the persisted ones. E.g. you want to allow developers to execute arbitrary GraphQL operations on your production server.
This can be achieved using the allowArbitraryOperations
option.
usePersistedOperations({
allowArbitraryOperations: (request) =>
request.headers.request.headers.get('x-allow-arbitrary-operations') ===
'true'
})
Use this option with caution!
Using Relay's Persisted Queries Specification
If you are using Relay's Persisted Queries specification, you can configure the plugin like below;
import { createYoga } from 'graphql-yoga'
import { createServer } from 'node:http'
import { usePersistedOperations } from '@graphql-yoga/plugin-persisted-operations'
import persistedOperations from './persistedOperations.json'
const yoga = createYoga({
plugins: [
usePersistedOperations({
getPersistedOperationKey(params: GraphQLParams & { doc_id: string }) {
return params.doc_id
}
getPersistedOperation(key: string) {
return persistedOperations[key]
},
}),
],
})
const server = createServer(yoga)
server.listen(4000, () => {
console.info('Server is running on http://localhost:4000/graphql')
})