Getting Started with GraphQL

This section documents the current GraphQL specification (October 2021), plus the working draft for @defer/@stream/@oneOf, and the GraphQL-over-HTTP specification, as published at graphql.org and spec.graphql.org — which are the references these pages are written and verified against — and, for the integration pages, against Spring for GraphQL (2.0.x), Strawberry, Ariadne, and the client docs for Apollo Client / urql / Relay. No specific patch version is pinned. Some surfaces (Apollo Router/managed federation, graphql-ws internals, the Relay compiler internals, GraalVM native) are linked rather than documented in depth.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as GraphQL tooling iterates quickly.

GraphQL is a query language for APIs together with a runtime that fulfills those queries against your existing data and services. This page introduces the model, the problems it solves relative to REST, the shape of a request and response, the tools used to explore a schema interactively, and the request lifecycle that every later page in this section builds on.

What GraphQL is

GraphQL is not a database, a storage format, or a specific server product — it is an open specification that describes a type system for declaring an API’s capabilities, a syntax for clients to ask for exactly the data they need, and a runtime contract for how a server resolves those requests into results. A GraphQL API is typically served from a single endpoint (conventionally /graphql), and every request — whether it reads or writes data — is a POST (or, for simple queries, sometimes a GET) carrying a query document rather than a URL path that identifies a resource.

The specification is maintained by the GraphQL Foundation, a neutral home for the project under the Linux Foundation, with an open governance process and public working groups. Two documents matter most in practice: the stable October 2021 specification, which is what virtually every production server and client implements today, and an ongoing working draft that incubates features such as @defer, @stream, and @oneOf input types ahead of their own eventual dated release. The best starting point for the concepts themselves — rather than the formal grammar — is graphql.org’s Introduction, which this section assumes as background and does not repeat wholesale.

A GraphQL schema describes every type, field, and operation an API exposes, and that schema is itself queryable through introspection — a client (or one of the interactive tools described later on this page) can ask the server what it supports before ever writing a query by hand. That combination — a single endpoint, a strongly typed schema, and client-specified field selection — is what distinguishes GraphQL’s model from a REST API built around many URL-addressed resources.

The problems GraphQL targets

GraphQL was designed against specific pain points that show up as a REST API and its client applications grow, rather than as a wholesale replacement for REST in every scenario:

  • Over-fetching. A REST endpoint returns a fixed shape for a resource, so a client that only needs a user’s name and avatar still receives every other field the endpoint happens to return. GraphQL lets the client select exactly the fields it needs, and nothing else, in the query itself.

  • Under-fetching. The opposite problem: a REST endpoint doesn’t return enough, forcing the client into several follow-up requests to assemble one screen (an order, then its line items, then each product). GraphQL lets a single query traverse those relationships and return one composed result.

  • Request waterfalls. Under-fetching compounds when each follow-up request depends on data from the previous one, so requests execute sequentially instead of in parallel — a chain of round trips whose latency adds up. A GraphQL server resolves the equivalent graph of data server-side, in one client round trip.

  • Endpoint sprawl. REST APIs tend to accumulate purpose-built endpoints and query parameters as new client screens need new shapes of data (/users/42/summary, /users/42?fields=name,avatar, and so on), each one another thing to version, document, and maintain. A GraphQL schema instead grows by adding types and fields that any client can combine on its own.

  • Weak typing at the API boundary. A REST response is only as well described as its out-of-band documentation (an OpenAPI spec, if one exists and stays current). GraphQL’s schema is the API’s single source of truth for its type system, enforced by the runtime on every request — a query asking for a field that doesn’t exist, or passing an argument of the wrong type, is rejected before any resolver runs.

None of this makes REST wrong for every use case — a small, stable API with few clients may never feel these pressures — but they are the concrete problems GraphQL exists to address, and they explain most of its design decisions: a single endpoint, client-specified selection sets, and a typed schema enforced by the server. See Tips: When to Use GraphQL or a Typical REST API for a fuller comparison of when each model is the better fit.

The request/response envelope

A GraphQL request is a small JSON (or form-encoded) payload with, at minimum, a query string, plus optional variables and an operation name when the query document defines more than one operation:

{
  "query": "query GetBook($id: ID!) { book(id: $id) { title author { name } } }",
  "variables": { "id": "42" }
}

Separating the query text from its variables lets a client reuse the exact same query string across requests (and lets a server cache or persist it) while only the argument values change from call to call.

Every response — success or failure — is wrapped in the same top-level JSON envelope, escaped here as \{ data, errors }: a data key holding the requested fields (or null), and an optional errors key holding an array of error objects when something went wrong during execution. Both keys can be present at once, because GraphQL supports partial success: one broken field in a large query does not have to fail the entire response. Response & error handling covers that envelope, its errors object shape, and partial-success semantics in depth.

Tooling for exploring a schema

Because a GraphQL server exposes its whole schema through introspection, a handful of general-purpose tools can explore any GraphQL API without generated client code, by reading the schema and offering autocomplete, inline documentation, and a query editor against it:

Tool What it is

GraphiQL

The original in-browser IDE for GraphQL: a query editor with autocomplete and a schema documentation explorer, commonly embedded by a server at its own endpoint for local development.

Apollo Sandbox

A hosted, no-install GraphQL IDE from Apollo, pointed at any endpoint’s URL; adds a schema reference, response tracing, and query history on top of the GraphiQL-style editor experience.

GraphQL Playground

An earlier, similarly capable in-browser (and desktop) IDE, historically bundled by several server frameworks before GraphiQL’s own tooling matured; still seen in older projects.

To try any of these against a live schema rather than a toy example, several public GraphQL APIs are commonly used for experimentation and tutorials — GitHub’s own API (https://docs.github.com/graphql), the SpaceX community API, and Countries GraphQL API among them. Pointing Apollo Sandbox or GraphiQL at one of those endpoints and browsing its schema is a fast way to get a feel for real-world queries before writing a server or client of your own.

A first query

Given a schema that exposes a book field, a client asks for exactly the fields it needs, nesting the selection to follow relationships in a single round trip:

query GetBook($id: ID!) {
  book(id: $id) {
    title
    publishedYear
    author {
      name
    }
  }
}

The server resolves it against the \{ "id": "42" } variables shown earlier and returns a response shaped to match the selection set exactly — no extra fields, and the nested author object assembled server-side rather than fetched by a second client request:

{
  "data": {
    "book": {
      "title": "The Left Hand of Darkness",
      "publishedYear": 1969,
      "author": {
        "name": "Ursula K. Le Guin"
      }
    }
  }
}

Notice the response tree mirrors the query tree field for field — that symmetry is one of GraphQL’s most useful properties for reading and debugging both sides of an API call.

The request lifecycle

Every GraphQL request — this section’s first query included — moves through the same four stages, with an error path that can branch off execution and propagate null up through the response rather than failing the whole request outright:

flowchart LR A[Parse] --> B[Validate] B --> C[Execute] C --> D[Respond] C -.error/null propagation.-> D

Parse turns the query string into an abstract syntax tree, rejecting anything that isn’t syntactically valid GraphQL. Validate checks that tree against the schema — every field, argument, and type must exist and agree with what the schema declares — before any application code runs. Execute walks the validated tree calling a resolver function per field, assembling the result (and where a field’s resolver throws or returns null for a non-nullable field, propagating that failure upward according to the spec’s null-propagation rules rather than aborting the other, unrelated branches of the query). Respond serializes whatever data was produced alongside any collected errors into the envelope described above.

This diagram is the canonical reference for the request lifecycle across this whole GraphQL section rather than something each page redraws: Execution & resolvers goes deep on the execute stage — how resolvers are invoked, how their results are collected, and how errors propagate through a partial result — and Response & error handling goes deep on the respond stage and the exact shape of the errors array. Both pages point back to the flowchart above instead of repeating it.