⚡️ Pro Feature ⚡️ This feature is bundled with GraphQL-Pro.
GraphQL::Pro::OperationStore
uses ActiveRecord
and Rack
to maintain a normalized, deduplicated database of persisted queries for your GraphQL system.
In this guide, you’ll find:
OperationStore
works, in briefIn other guides, you can read more about:
OperationStore
in your appAlso, you can find a demo app on GitHub.
Persisted queries are GraphQL queries (query
, mutation
, or subscription
) that are saved on the server and invoked by clients by reference. In this arrangement, clients don’t send GraphQL queries over the network. Instead, clients send:
Then, the server uses the identifier to fetch the full GraphQL document from the database.
Without persisted queries, clients send the whole document:
# Before, without persisted queries
query_string = "query GetUserDetails($userId: ID!) { ... }"
MyGraphQLEndpoint.post({
query: query_string,
operationName: "GetUserDetails",
variables: { userId: "100" },
})
But with persisted queries, the full document isn’t sent because the server already has a copy of it:
# After, with persisted queries:
MyGraphQLEndpoint.post({
operationId: { "relay-app-v1/fc84dbba3623383fdc",
# client name / query alias (eg, @relayHash)
variables: { userId: "100" },
})
Using persisted queries improves the security, efficiency and visibility of your GraphQL system.
Persisted queries improve security because you can reject arbitrary GraphQL queries, removing an attack vector from your system. The query database serves a whitelist, so you can be sure that no unexpected queries will hit your system.
For example, after all clients have migrated to persisted queries, you can reject arbitrary GraphQL in production:
# app/controllers/graphql_controller.rb
if Rails.env.production? && params[:query].present?
# Reject arbitrary GraphQL in production:
render json: { errors: [{ message: "Raw GraphQL is not accepted" }]}
else
# ...
end
Persisted queries improve the efficiency of your system by reducing HTTP traffic. Instead of repeatedly sending GraphQL over the wire, queries are fetched from the database, so your requests require less bandwidth.
For example, before using persisted queries, the entire query is sent to the server:
/operation_store/request_before.png
But after using persisted queries, only the query identification info is sent to the server:
/operation_store/request_after.png
Persisted queries improve visibility because you can track GraphQL usage from a single location. OperationStore
maintains an index of type, field and argument usage so that you can analyze your traffic.
/operation_store/operation_index.png
OperationStore
uses tables in your database to store normalized, deduplicated GraphQL strings. The database is immutable: new operations may be added, but operations are never modified or removed.
When clients sync their operations,%20requests%20are%20%5Bauthenticated%5D(/operation_store/access_control), then the incoming GraphQL is validated, normalized, and added to the database if needed. Also, the incoming client name is associated with all operations in the payload.
Then, at runtime, clients send an operation ID to run a persisted query. It looks like this in params
:
params[:operationId] # => "relay-app-v1/810c97f6631001..."
OperationStore
uses this to fetch the matching operation from the database. From there, the query is evaluated normally.
See the getting started guide to add OperationStore
to your app.