Skip to content

Quickstart: GraphQL

GraphQL only For a REST or other plain HTTP origin, follow the HTTP quickstart instead.

This takes you from a new account to a verified cache HIT in front of your own GraphQL API, and then shows a mutation clearing that entry. Nothing here is throwaway: the service you create and the config you write are the ones you keep. If your origin is not a GraphQL API, follow the HTTP quickstart.

Before you start you need a GraphQL API that GraphPilot can connect to over the internet. It does not have to be public. The examples use a products query and an updateProduct mutation; substitute an operation from your own API.

  1. Sign up at cockpit.graphpilot.io and create an organization with New organization if you do not belong to one yet; the dialog asks you to accept the Terms of Service. Then install the gpilot CLI and log in:

    Terminal window
    brew install graphpilot/tap/gpilot
    gpilot auth login

    Other platforms: installing the CLI. Several organizations or identities: signing in.

  2. A service is one origin behind GraphPilot, with its own hostname, configuration and cache.

    In the portal, choose New service on your organization’s page. The Create a new service dialog asks for a Service name (Checkout API), an Origin URL (https://api.internal.example.com), a Subdomain (checkout-api) and an Environment. See creating a service for what each field accepts. Keep Deploy right away (no schema, default configuration) ticked.

    Select Create service. The next dialog shows the service’s signing key, once. Copy it into your secret manager, then select I have copied the key.

    The service’s dashboard now shows Setting up checkout-api: GraphPilot deploys the service once as a plain pass-through, then looks for your GraphQL API behind it and shows what it found, such as the endpoint path and the number of types. Apply suggested configuration and schema turns GraphQL on with the schema it read. You can apply it, or select Keep as plain HTTP: step 5 deploys your own schema.graphql either way, because a schema read by introspection carries no caching directives. See set up a new service for every state of the setup.

    Then select the service in the CLI:

    Terminal window
    gpilot service select checkout-api

    Once the first deployment is live, your service is reachable at https://checkout-api.k7m2qp.graphpilot.cloud (k7m2qp stands for your organization’s hostname label, which the portal shows). That takes one to two minutes, and for your organization’s first service a few minutes more, up to an hour, while its certificate is issued; the setup shows both. Until you deploy a schema, it is a plain pass-through: every request reaches your origin unchanged, and only responses your origin marks with Cache-Control are cached.

    GraphPilot forwards each request to your origin’s host with the path the client sent, so if your API answers on /graphql, your clients call https://checkout-api.k7m2qp.graphpilot.cloud/graphql. How the origin URL is used (scheme, port, TLS) is on origin.

  3. In the directory you will deploy from, you need two files.

    schema.graphql is your API’s schema, exactly as your server exposes it. GraphPilot reads it for your types, the key fields it derives surrogate keys from, and any caching directives. If you have no schema file, read one from your running API with gpilot schema pull, then add the directives from step 4 to it: introspection returns your types but no applied directives such as @cacheControl.

    gpilot.toml is the service configuration. Every setting has a default, so an empty file is valid. GraphPilot treats requests to /graphql as GraphQL. If your API answers on a different path, say so: until you do, a GraphQL operation sent to that path is refused with 404 and NOT_A_GRAPHQL_PATH, because the GraphQL checks run only on the paths listed here:

    gpilot.toml
    [request]
    graphql_paths = ["/api/graphql"]
  4. GraphPilot does not decide on its own that a response may be reused. Your origin or your schema has to say so. Pick either; you can use both later.

    Send a Cache-Control header on the responses that may be cached:

    Cache-Control: s-maxage=60

    A header from your origin decides before your schema is consulted. How the two combine, and why s-maxage rather than max-age, is on when a response is cached.

  5. Terminal window
    gpilot deploy

    This picks up ./schema.graphql and ./gpilot.toml, shows what changed since the last deployment, and asks before uploading anything. It then waits for the deployment to go live, printing each step and any findings the build reported. A failed build never goes live. Flags for CI, other paths and not waiting are in the command reference.

  6. Send the same query twice to your service hostname:

    Terminal window
    export GRAPHPILOT_HOST=checkout-api.k7m2qp.graphpilot.cloud
    curl -sS -D- -o/dev/null "https://$GRAPHPILOT_HOST/graphql" \
    -H 'content-type: application/json' \
    -d '{"query":"{ products { id name } }"}' | grep -i '^gp-'

    Use your API’s GraphQL path in place of /graphql if you set request.graphql_paths in step 3.

    The first response is a MISS: it came from your origin and was stored. The second is a HIT:

    gp-cache: HIT
    gp-cache-hits: 1
    gp-cache-age: 4
    gp-cache-max-age: 60
    gp-cache-remaining: 56

    If you see PASS, read the next section.

  7. Send a mutation through GraphPilot that changes one of the products in that list and returns it:

    Terminal window
    curl -sS -D- -o/dev/null "https://$GRAPHPILOT_HOST/graphql" \
    -H 'content-type: application/json' \
    -d '{"query":"mutation { updateProduct(id: \"1\", name: \"Blue shirt\") { id name } }"}'

    Repeat the query from step 6. The first response is a MISS again, with the new name from your origin, and the one after it is a HIT.

    Nothing was configured for this. The cached products response was tagged with a surrogate key for each product it contained, such as Product:id:1. The mutation’s response contained product 1, so GraphPilot purged every cached response tagged Product:id:1. A mutation purges only what it returns: see automatic invalidation for what is and is not purged.

gp-cache: PASS means the response was not stored, and gp-cache-reason names the rule that refused. These three cause most first-deployment misses:

  • Your origin sets a cookie (CACHE_SKIPPED_SET_COOKIE). A response that sets a session cookie is never stored. Frameworks that touch the session on every request do this by default. See the refusals.
  • Apollo Server sent no-store (CACHE_SKIPPED_NO_STORE). It does so for every response its hints do not make cacheable. See Apollo Server.
  • Your hosting platform added Cache-Control: private. Some platforms, such as Google Cloud Run with session affinity, add it to every response that has no Cache-Control of its own. GraphPilot then stores the response per caller, or not at all. See when your hosting platform adds Cache-Control: private.

Every other reason is in why is nothing cached?, and a second MISS where you expected a HIT in troubleshooting.

Change your client’s endpoint to the service hostname, keeping the path your API uses. Nothing else changes: GraphPilot speaks the same GraphQL over the same transports, and what reaches your origin is an ordinary GraphQL request.

import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
link: new HttpLink({ uri: "https://checkout-api.k7m2qp.graphpilot.cloud/graphql" }),
cache: new InMemoryCache(),
});

Before you consider this finished, verify the request signature at your origin, so it only trusts traffic that came through GraphPilot, and keep your origin’s clock in sync, because the signature is time-bound. Both are on the go-live checklist.

You want toRead
Understand exactly what gets cached and what does notWhen a response is cached
Decide who shares a cached answerWho shares an entry
Know what a mutation purges, and what it does notAutomatic invalidation
Clear something by handThe purge API
Stop clients sending arbitrary queriesPersisted queries
Deploy from a pipelineDeploy from CI
See every settingConfiguration reference
Serve it on your own hostnameCustom domains