GraphPilot: GraphQL edge proxy for automatic caching, security and optimization

GraphPilotWE FLY THE COMPLEXITY SO YOU DON'T HAVE TO
About

Built for GraphQL.

Works with everything.

GraphPilot is a GraphQL-native proxy, written in Rust, that sits in front of your API and handles caching, security, and optimization for you. It reads your schema to work out which queries to cache, how long to hold them, and which entries to clear when a mutation changes the data underneath. There are no cache keys to maintain and no purge scripts to write, and nothing to wire up before it starts working.

That is harder than it sounds, because GraphQL breaks the assumptions most infrastructure is built on. Every request goes to the same endpoint, so ordinary HTTP caching has nothing to key on. A single query can nest deep enough to pull far more work out of your servers than you intended. And with no fixed response shape, clearing the cache after a write is one of the harder problems to get right.

Client

Web · Mobile · Server

Request
Response
GraphPilot
PROTECT CACHE OPTIMIZE
Request
Response

Origin

GraphQL · REST · HTTP

New York Los Angeles Chicago Toronto São Paulo London Frankfurt Amsterdam Paris Stockholm Mumbai Singapore Tokyo Sydney + 65 more

GraphPilot runs on a global edge network that sits between your clients and your origin, close enough to most users to serve a cached read without crossing regions. Anything it can't answer from cache, it checks, deduplicates, and forwards upstream clean, so your origin only handles what has to reach it. That work falls into four areas, each one handled at the edge.

Edge Cache

GraphQL reads served from the nearest PoP, with schema-aware invalidation after writes and empty-result caching handled correctly.

Learn more
Edge Optimization

Request collapsing, persisted operations, GET-cacheable reads, and faster edge transport for leaner API traffic.

Learn more
Edge Security

GraphQL-aware abuse protection, auth policies, query-shape controls, and operation rules before traffic reaches origin.

Learn more
Insights

Operation-level traffic, cache health, security activity, and edge geography without SDKs or log shipping.

Learn more
Why GraphPilot

Six reasons.

To fly with GraphPilot.

Schema-driven

Your schema is the single source of truth.

In GraphQL the schema is already the contract every client and service agrees on. GraphPilot makes it the source of truth for behavior too: caching, auth, rate limits, and invalidation are declared as directives on the fields they govern, instead of sitting in a separate dashboard or config repo that quietly drifts out of sync.

Read more
Selection set expansion

Clients write GraphQL. We write the keys.

Cache-key fields injected upstream, stripped before they reach the client. Clients never decide which fields make a response cacheable. That responsibility moves to the backend team and lives in the schema.

Read more
Dedicated binary

One binary per tenant and service.

Written in Rust, compiled to a dedicated WASM binary for each. Isolated, never a shared multi-tenant runtime.

Universal edge

GraphQL, REST, any HTTP.

One edge in front of every API you run.

European

European, under German law.

GDPR-native · EUR pricing · German jurisdiction.

Cloud roadmap

Fastly today, European tomorrow.

Provider-agnostic by design. Sovereign EU clouds (Telekom Cloud, StackIT, IONOS) are planned.

Pre-flight boarding is open

Reserve your seat for early access.
No credit card, no commitment, just a head start.

Schema-driven

One schema.

One truth.

With GraphPilot, your GraphQL schema becomes the configuration. Caching, authorization, rate limits, and invalidation are declared as directives right next to the fields they govern, instead of being scattered across config files, dashboards, and purge scripts.

Because the policy travels with the type, a schema change carries its edge behavior with it. The rules are versioned in the same repository, reviewed in the same pull request, and readable by everyone who already works with the schema.

schema.graphql
enum Role {
  BUYER @policy(signal: "auth:jwt.claims.role == 'buyer'")
  ADMIN @policy(signal: "auth:jwt.claims.role == 'owner'")
  ADMIN @policy(signal: "auth:jwt.claims.role == 'admin'")
}

schema
  @operationPolicy(maxDepth: 12, introspection: ALLOW)
  @operationPolicy(maxDepth: 8, introspection: BLOCK)
{
  query: Query
  mutation: Mutation
}

type Product
  @cacheControl(maxAge: 300, scope: PUBLIC)
  @auth(requires: BUYER) {
  id: ID! @surrogateKey
  name: String! @cacheControl(maxAge: 600)
  price: Float
    @cacheControl(maxAge: 120, scope: PUBLIC)
    @cacheControl(maxAge: 60, scope: PRIVATE)
}

type Query {
  product(id: ID!): Product
    @rateLimit(limit: "1000/m", by: IP)
    @rateLimit(limit: "600/m", by: IP)
}

type Mutation {
  updateProduct(id: ID!): Product
    @auth(requires: ADMIN)
}

Versioned with your types

Cache TTL, auth rules, and invalidation keys live next to the field definitions they govern. When a type changes, the policy changes in the same pull request.

Visible to every team

Backend engineers, platform engineers, and API consumers all read the same schema. They see the same edge behavior without separate docs to maintain.

Works alongside what you have

Apollo Federation cache hints and HTTP cache-control headers keep working. GraphPilot extends the schema you already own instead of replacing it.

Schema reviews become infrastructure reviews.

@cacheControl

Set TTL, scope, and stale-while-revalidate right on the field. Caching policy stays versioned with the schema.

@surrogateKey

Mark the entity key once and let GraphPilot purge exactly the rows that depend on it.

@policy

Bind request context or claims to a schema-level decision rule before the operation ever reaches origin.

@rateLimit

Cap reads and mutations per principal, tenant, or org so noisy traffic stays contained.

@auth

Attach role checks to types and fields so access stays close to the graph it protects.

@operationPolicy

Block expensive shapes, deep nesting, or introspection before the request can do real work.

Edge Cache

From slow.

To blazing fast.

GraphPilot turns GraphQL reads into edge-cacheable responses without changing your clients or your resolvers. The first request populates the cache. Every repeat read for the same operation and variables is served from the nearest edge location on a global network, designed to return in well under 50ms.

Single POP Multiple POPs Planned new POP Planned upgrade
Map reflects the Fastly network GraphPilot deploys to, current as of June 8, 2026. Subject to change. Source: Fastly .
Fastly is a trademark of Fastly, Inc. GraphPilot is not affiliated with or endorsed by Fastly.

A cache hit returns immediately from the closest edge location. A miss is forwarded upstream and reused for matching operations and variables. The origin stays authoritative for fresh data and mutations, but repeat reads stop crossing regions: latency drops because the response is already near the client, and origin load drops because repeated query shapes no longer execute the same resolver tree.

The hard part is not storing a response. The hard part is knowing what to purge after a mutation. GraphPilot reads your schema, derives cache tags automatically, injects the key fields needed for invalidation, and removes exactly the entries affected by a write.

Selection set expansion

Added upstream.

Removed downstream.

Clients keep writing ordinary GraphQL. If a requested cache key is not present in the client document, GraphPilot attaches the field upstream under an internal alias, reads it for cache tagging, and removes it again before the response reaches the client.

That keeps the invalidation contract close to the schema instead of leaking cache-management fields into Apollo, Relay, or handwritten GraphQL clients.

It also takes a decision off the client entirely. Application developers no longer have to know which key fields a query must request for its response to be cacheable. They ask for the data they need, and the backend team owns cacheability through the schema.

type User {
  id: ID! @surrogateKey
  name: String!
}

query GetUser {
  user(id: "42") {
    name
    _gp_id: id # injected upstream
  }
}
Automatic invalidation

Mutations purge

exactly what changed.

GraphPilot does not depend on hand-written purge rules. Reads become tagged cache entries; mutation responses become purge targets for the same tag model. When a user is removed, matching user reads and list results are cleared automatically.

Cache tags stay derived from the graph instead of being maintained by hand, so a schema change updates the invalidation contract with the API shape it belongs to.

query GetUser {
  user(id: "42") {
    id
    name
  }
}

mutation {
  removeUser(id: "42") {
    _gp_id: id # invalidates query above
  }
}
Empty result caching

Empty lists count as

real cache entries.

An empty GraphQL list is still a meaningful result. GraphPilot can cache that response and tag it with a dedicated empty-list surrogate key such as !Product, instead of treating it as an uncacheable dead end.

GraphPilot does not try to understand every filter combination or search rule. When a product mutation could make an empty product list non-empty, it invalidates the cached empty product lists for that key. The next read goes back to origin and repopulates the edge cache with the current result.

query SearchProducts {
  products(category: "new") {
    id
  }
}

mutation {
  createProduct(category: "new") {
    id # purges !Products
  }
}
Edge Optimization

From wasteful.

To lean.

Edge caching is one lever. The bytes leaving a client travel through a chain of smaller decisions that together decide whether a response arrives in tens of milliseconds or several hundred: how many requests reach origin, how big they are on the wire, how many caches between client and edge can reuse them, how quickly the connection itself can be opened.

GraphPilot pulls those decisions to the edge and applies them by default. No SDK changes, no special headers, no per-client tuning. Every read benefits the moment it routes through GraphPilot.

Request collapsing
A hundred clients.
One origin call.

When clients pile up on the same query at the same moment, GraphPilot collapses the duplicates and fires one request upstream. Every waiting client receives the response. Your origin sees one call.

With Origin Shielding enabled, collapsing extends across the entire network: cache misses from any edge location route through a single shield node first, so a global stampede still lands at origin as exactly one request.

Tablet
Laptop
Desktop
GraphPilot
Origin
Persisted operations
Hash over the wire.
Operation at the edge.

Once an operation is registered, clients send a short hash instead of the full query text. GraphPilot resolves the hash to the stored operation at the edge, skips parsing, and forwards a normalized request upstream.

Request bodies shrink from kilobytes to bytes, validation cost drops to a lookup, and only approved operations can ever reach the graph.

POST /graphql HTTP/2
Content-Type: application/json

{
  "extensions": {
    "persistedQuery": {
      "version": 1,
      "sha256Hash": "9f1c4a…b4e2"
    }
  },
  "variables": { "id": "42" }
}
GET-cacheable reads
One hash.
Many cache layers.

Persisted reads can travel as plain HTTP GET requests. That turns the response into something every cache between the user and the edge can store: the browser HTTP cache, service workers, corporate proxies, ISP caches.

The fastest GraphQL request is the one that never leaves the client. The second-fastest is the one resolved by a cache that does not belong to GraphPilot at all.

GET /graphql?id=9f1c4a…b4e2&variables=%7B%22id%22%3A%2242%22%7D
Cache-Control: public, max-age=60, stale-while-revalidate=600
Vary: Accept-Encoding

# served by, in order of proximity:
#   browser HTTP cache
#   service worker
#   corporate / ISP proxy
#   GraphPilot edge
#   origin
Edge transport
Fewer bytes.
Faster handshakes.

Every response served from GraphPilot is Brotli-compressed at the edge, typically smaller on the wire than gzip for JSON payloads. HTTP/3 over QUIC cuts much of the connection-setup tax that dominates mobile and lossy networks.

No flag to flip, no client capability to detect. GraphPilot negotiates the best transport per client and falls back gracefully when something older is on the other end.

gzip
48.2 KB
Brotli
38.6 KB
Typically smaller than gzip on JSON
HTTP/2
3 RTTs
HTTP/3
1 RTT
QUIC removes TCP + TLS negotiation
Edge Security

From exposed.

To edge-protected.

GraphQL abuse rarely looks like one loud endpoint. It can be a cheap HTTP request with an expensive selection set, a mutation loop from one account, or schema probing that should never reach production.

GraphPilot applies GraphQL-aware security controls before traffic hits your origin. Slow down abusive clients, reject untrusted requests, and block dangerous query shapes while your application stays focused on business logic.

Most of these controls start by reading something out of the request, like a tenant from a JWT claim, a region from a header, or a locale from a cookie. To express that, we built a small language of our own called Radiogramm. It runs simple queries against the incoming request and shapes the result with a handful of operators, so a rate limit, a cache key, or an auth signal can be derived straight from request context. A few examples are shown below.

TransmittingFREQ 001-006
Input
Auth: org = ACME
Signal

lower(auth:test.org)

Normalize the auth-backed tenant before it becomes part of a shared key.

Output
acme
Rate limiting

Control operations.

Not just requests.

A single /graphql endpoint can hide very different workloads. GraphPilot keeps traffic controls close to the graph, so operations, principals, and sensitive mutation fields can each carry their own protection model.

Cacheable reads can keep flowing while repeated mutations, scraping patterns, or one noisy organization are slowed down before resolver time is spent.

enum Principal {
  USER_ID @policy(signal: "auth:jwt.claims.sub")
  ORG_ID  @policy(signal: "auth:jwt.claims.org_id")
}

type Query {
  product(id: ID!): Product
    @rateLimit(limit: "600/m", by: ORG_ID)
}

type Mutation {
  checkout(input: CheckoutInput!): Checkout
    @rateLimit(limit: "20/m", by: USER_ID)
}
Authorize

Authorize at

the graph boundary.

GraphPilot models authorization as schema policy. Roles map to policy signals, and @auth can be applied to objects, fields, interfaces, inputs, and mutations.

Identity providers, JWKS endpoints, and network allowlists are global deployment concerns. The schema consumes the verified identity and defines what parts of the graph it can reach.

enum Role {
  ADMIN    @policy(signal: "auth:jwt.claims.role == 'admin'")
  CUSTOMER @policy(signal: "auth:jwt.claims.role == 'customer'")
}

type Mutation {
  checkout(input: CheckoutInput!): Checkout
    @auth(requires: CUSTOMER)
}

type Customer @auth(requires: CUSTOMER) {
  id: ID!
  email: String! @auth(requires: ADMIN)
}
Query shape protection

Block expensive

GraphQL shapes.

Some of the most expensive GraphQL requests are syntactically valid: deep nesting, alias spam, too many root fields, large variables, batched operations, or production introspection.

GraphPilot parses the operation before forwarding it and blocks unsafe shapes at the edge. The MVP starts with depth limits and production introspection blocking, with scoped overrides for roles like admin, customer, and anonymous users.

enum Role {
  ADMIN @policy(signal: "auth:jwt.claims.role == 'admin'")
  ANONYMOUS @policy(signal: "!auth:jwt.claims.sub")
  CUSTOMER @policy(signal: "auth:jwt.claims.role == 'customer'")
}

schema
  @operationPolicy(maxDepth: 8, introspection: BLOCK)
  @operationPolicy(for: ADMIN, maxDepth: 16, introspection: ALLOW)
  @operationPolicy(for: [ANONYMOUS, CUSTOMER], maxDepth: 4) {
  query: Query
  mutation: Mutation
}
Insights

From blind.

To informed.

Everything GraphPilot does at the edge happens out of sight. Without visibility you cannot tell whether the cache is absorbing the traffic it should, whether a security rule is acting on the right principal, or whether users in Berlin are being served from a nearby location instead of crossing the continent.

The GraphPilot portal is designed to make all of it visible by default, with nothing to instrument, no SDK to install, and no log shipping to configure. You can see where requests come from by country and edge location, which clients and user agents call each operation, how cache-hit ratios hold up per region, and where latency is best. Operations, cache health, and blocked requests each get their own view.

Portal screens below show illustrative example data.

Operation insights

Per operation.

Not per endpoint.

GraphPilot groups traffic by GraphQL operation name, not by HTTP endpoint. GetProduct, Checkout, and ListUsers each get their own row in the portal: volume, p95 latency, error rate, cache-hit ratio.

When a single mutation slows down or starts erroring, you see it immediately. A generic APM dashboard averaging everything behind POST /graphql would have hidden the signal in the noise.

Operations
Operation p95 Err Hit
GetProduct 18ms 0.02% 94%
Checkout 380ms 2.10%
ListUsers 42ms 0.10% 71%
SearchProducts 55ms 0.05% 62%
CreateOrder 95ms 0%
Cache health

Hits, misses, stales.

At a glance.

The portal shows hit, stale, and miss ratios aggregated and per operation, so you see exactly how much origin traffic the edge cache is absorbing, and where it isn't.

Every mutation lists the surrogate keys it invalidated. If automatic invalidation does not clear what you expect, you find out immediately, not after a stale read reaches production.

Cache
86% hit 6% stale 8% miss
Top cached
GetProduct
94%
ListUsers
71%
SearchProducts
62%
Security activity

Every block,

with the reason.

Traffic-control decisions, auth rejections, blocked query shapes. Every decision GraphPilot takes against a request is logged with the operation, the principal, and the rule that fired.

A noisy organization surfaces as a clear pattern. A spike in introspection attempts surfaces immediately. Nothing security does at the edge is silent.

Blocked requests
  1. RATE_LIMIT GetProduct org:acme 600/m exceeded
  2. AUTH Checkout anon CUSTOMER required
  3. SHAPE __schema anon introspection blocked
  4. RATE_LIMIT Checkout user:u_91f3 20/m exceeded
  5. SHAPE ListUsers anon depth > 4
GraphPilot

Pre-flight boarding is open

Reserve your seat for early access.
No credit card, no commitment, just a head start.