PyPI version Status GitHub stars Downloads

Automatic Persisted Queries

Bandwidth savings, security hardening, and pre-compiled query execution.

What is APQ?

Automatic Persisted Queries (APQ) allows clients to send a short hash instead of the full GraphQL query text. The server stores registered queries and looks them up by hash, providing bandwidth savings and enabling security hardening.

APQ caches queries, not results

APQ persists the query definition (the GraphQL text), not query results. Each request still executes against the database with fresh data. The benefits are bandwidth reduction and skipping query parsingβ€”not result caching.

Benefit Without APQ With APQ
Request Payload Full query text (1-10KB) SHA256 hash (64 bytes)
Bandwidth Usage 100% ~30% (70% reduction)
Query Parsing Parse on every request Pre-parsed, lookup only
Security Control Any query accepted Can enforce registered-only

How It Works

APQ Request Flow

Client                           FraiseQL                         Database
  β”‚                                  β”‚                                 β”‚
  β”‚  POST /graphql                   β”‚                                 β”‚
  β”‚  { "extensions": {               β”‚                                 β”‚
  β”‚      "persistedQuery": {         β”‚                                 β”‚
  β”‚        "sha256Hash": "abc123"    β”‚                                 β”‚
  β”‚      }                           β”‚                                 β”‚
  β”‚    }                             β”‚                                 β”‚
  β”‚  }                               β”‚                                 β”‚
  │─────────────────────────────────▢│                                 β”‚
  β”‚                                  β”‚                                 β”‚
  β”‚                          1. Lookup hash in                         β”‚
  β”‚                             APQ store                              β”‚
  β”‚                                  β”‚                                 β”‚
  β”‚                          2. Get pre-parsed                         β”‚
  β”‚                             query definition                       β”‚
  β”‚                                  β”‚                                 β”‚
  β”‚                          3. Execute query ───────────────────────▢│
  β”‚                                  β”‚                                 β”‚
  β”‚                                  │◀──────────────────────────────  β”‚
  │◀─────────────────────────────────│                                 β”‚
  β”‚  { "data": { ... } }             β”‚                                 β”‚

1. First Request (Registration)

# Client sends full query + hash
POST /graphql
{
  "query": "query GetUsers { users { id name } }",
  "extensions": {
    "persistedQuery": {
      "version": 1,
      "sha256Hash": "abc123..."
    }
  }
}

# FraiseQL:
# 1. Validates hash matches query
# 2. Stores query in APQ store
# 3. Executes and returns result

2. Subsequent Requests

# Client sends only the hash
POST /graphql
{
  "extensions": {
    "persistedQuery": {
      "version": 1,
      "sha256Hash": "abc123..."
    }
  }
}

# FraiseQL:
# 1. Looks up query by hash
# 2. Uses pre-parsed query
# 3. Executes and returns result
#
# Bandwidth: ~70% smaller request

Key Benefits

πŸ“‰

70% Bandwidth Reduction

Send a 64-byte hash instead of multi-KB query strings. Significant savings for mobile clients and large queries.

πŸ›‘οΈ

Security Hardening

Optional "persisted-only" mode blocks arbitrary queries. Only pre-registered queries can execute.

πŸš€

Skip Query Parsing

Registered queries are pre-parsed. No need to parse and validate GraphQL on every request.

🐘

PostgreSQL Backend

Queries stored in your database. No Redis needed. Survives restarts, shared across instances.

πŸ“Š

Query Analytics

Track which queries are used, how often, and by which tenants. Built-in observability.

Security: Persisted-Only Mode

For security-sensitive deployments, FraiseQL can enforce that only pre-registered queries are allowed. Arbitrary queries are rejected.

Persisted-Only Mode
from fraiseql.fastapi import FraiseQLConfig, create_fraiseql_app
from fraiseql.fastapi.config import APQMode

# Security-hardened: only persisted queries allowed
config = FraiseQLConfig(
    database_url="postgresql://localhost/db",
    apq_mode=APQMode.REQUIRED,  # Block arbitrary queries
    apq_queries_dir="./graphql/",  # Auto-register .graphql files
)

app = create_fraiseql_app(types=[User, Post], config=config)

# APQ Modes:
# - APQMode.OPTIONAL (default): Accept both hashes and full queries
# - APQMode.REQUIRED: Only accept persisted query hashes
# - APQMode.DISABLED: Ignore APQ extensions, always require full query

Security Benefits

  • βœ“ Block query exploration - Attackers can't probe your schema with arbitrary queries
  • βœ“ Audit all queries - Know exactly which queries can run in production
  • βœ“ Control API surface - Only queries from your codebase execute
  • βœ“ Prevent introspection abuse - Optionally block introspection queries

Configuration

Basic Setup
from fraiseql.fastapi import FraiseQLConfig, create_fraiseql_app

# APQ enabled by default (optional mode)
config = FraiseQLConfig(
    database_url="postgresql://localhost/db",
    # apq_mode defaults to APQMode.OPTIONAL
    # apq_storage_backend defaults to "memory"
)

app = create_fraiseql_app(types=[User, Post], config=config)
Storage Backends
from fraiseql.fastapi import FraiseQLConfig

# Memory Backend (default) - fast, single-server
# Good for development, queries lost on restart
config = FraiseQLConfig(
    database_url="...",
    apq_storage_backend="memory",
)

# PostgreSQL Backend (recommended) - persistent, production-ready
# Queries survive restarts, shared across instances
config = FraiseQLConfig(
    database_url="...",
    apq_storage_backend="postgresql",
)

FAQ

Does APQ cache query results?

No. APQ caches the query definition (the GraphQL text), not results. Each request still executes against the database with fresh data. For result caching, you'd use HTTP caching or a CDN.

How much faster is APQ?

APQ saves bandwidth and skips query parsing, but execution time depends on your database queries. The main win is 70% bandwidth reduction and avoiding repeated parsing of complex queries.

Is APQ compatible with Apollo Client?

Yes. FraiseQL implements the Apollo APQ specification. Apollo Client, Relay, and other GraphQL clients with APQ support work out of the box.

Get Started with APQ

APQ is enabled by default in FraiseQL. Zero configuration needed.