98 entries across the GraphQL surface: the schema language, execution, federation, the server and client libraries, and the security controls a public graph needs.
Topics
98
Sections
10
Client hooks
useQuery
Apollo's read hook: variables, loading, error and a normalized-cache-backed result.
It reads on render and returns loading, error and data backed by the normalised cache.
Two components asking for the same entity read one cache entry, not two requests.
A new object passed as variables on every render refetches instead of reusing.
useLazyQuery
Apollo query triggered from an event rather than on render.
The query is triggered from an event rather than when the component renders.
It returns an execute function, so nothing is requested until something calls it.
Calling it in render is the mistake that turns it back into a normal query.
useMutation
Apollo write hook returning a trigger and the request state, with cache update options.
It returns a trigger plus the request state, with options to update the cache.
Nothing refetches automatically; the cache update or refetch is declared.
A mutation returning the changed entity with its id updates views for free.
useSubscription
Apollo hook for a long-lived stream, wired to the link that carries it.
A long-lived stream is wired through whichever link carries the transport.
It needs a link that supports streaming; the HTTP link alone cannot.
Reconnection and missed events between drops are the application's problem.
useSuspenseQuery
Apollo read hook that suspends instead of exposing a loading flag.
The component suspends instead of rendering a loading branch.
Data is never undefined, so the type has no loading state to handle.
Two suspense queries in one component wait in series unless they are hoisted.
useBackgroundQuery / useReadQuery
Start a query high in the tree and read it lower down, without blocking the parent.
A query starts high in the tree and is read further down without blocking the parent.
Starting early is what removes the waterfall a child-initiated fetch creates.
The parent does not suspend, so only the reader shows a fallback.
useFragment
Subscribe a component to one fragment of cached data instead of a whole query result.
A component subscribes to one fragment of cached data rather than a whole result.
It re-renders only when the fields it declared change.
It reads the cache and never issues a request of its own.
useApolloClient
Reach the client for imperative reads, writes and cache eviction.
The client is reached for imperative reads, writes and eviction.
It is for event handlers; nothing it returns re-renders a component.
Writing to the cache by hand must keep the shape the query expects.
urql useQuery
urql's read hook, with a document cache by default and graphcache when configured.
It reads with a document cache by default, and normalises only with graphcache.
Document caching keys whole results, so one changed entity does not update elsewhere.
Adding graphcache changes invalidation behaviour across the whole application.
urql useMutation
urql write hook whose result shape mirrors its query hook.
The write hook mirrors the query hook's result shape.
A shared shape is what lets one renderer handle both.
Without normalisation, affected queries are invalidated rather than patched.
urql useSubscription
urql streaming hook, with a handler that folds each event into state.
A handler folds each streamed event into the accumulated state.
The handler owns the reduction, so ordering is explicit rather than assumed.
Without a handler only the latest event is visible.
useLazyLoadQuery
Relay hook that fetches on render, for screens without a preloaded query.
Relay fetches on render, for a screen with no preloaded query.
Fetching at render is a waterfall Relay would otherwise avoid.
It is the fallback, not the pattern Relay is designed around.
usePreloadedQuery
Relay hook that reads a query started before the component rendered.
A query started before the component rendered is read here.
Starting at navigation is what overlaps the fetch with the render.
The reference must be created outside the component that consumes it.
usePaginationFragment
Relay hook that pages a connection while keeping the fragment's data consistent.
A connection is paged while the fragment's data stays consistent.
Loaded pages stay rendered as the next one arrives.
It requires the connection to follow the cursor conventions Relay expects.
Relay useFragment
Declare exactly the fields a component reads, and let the compiler enforce it.
A component declares exactly the fields it reads and the compiler enforces it.
Reading a field the fragment did not declare is a build error, not a runtime surprise.
Its data requirement travels with it when it is reused elsewhere.
Schema language
type
An object type with fields — the nodes of the graph and the bulk of any schema.
An object type with fields makes up the nodes of the graph.
Fields are the edges, so the type graph is what clients traverse.
A type with no path from a root is unreachable however well it is defined.
Query
The root type for reads. Every query begins at one of its fields.
Every read begins at a field on this root type.
Its fields may resolve in parallel, unlike a mutation's.
What it exposes is the entire read surface of the API.
Mutation
The root type for writes. Fields run in series, unlike query fields.
Writes enter here, and top-level fields run one after another.
Serial execution is what makes two writes in one document deterministic.
Nested fields under a mutation resolve in parallel again.
Subscription
The root type for a long-lived stream of events pushed to the client.
A long-lived stream of events is pushed to the client from this root.
It needs a transport that stays open, unlike a query.
Each subscription is per client, so fan-out is a server concern.
input
An input object type. Inputs and outputs are deliberately separate types.
Input object types are deliberately separate from output types.
An output type cannot be used as an argument, which is why inputs are declared.
Inputs have no interfaces or unions, so variants are modelled differently.
interface
A set of fields that implementing types must provide — polymorphism with a shared contract.
Implementing types must provide the declared fields, giving polymorphism a shared contract.
A client can select the shared fields without knowing the concrete type.
Adding a field to an interface obliges every implementer at once.
union
A value that is one of several object types, with no shared fields. Resolved by __typename.
A value is one of several object types with no fields in common.
Selecting anything requires an inline fragment naming the type.
Clients need __typename to tell the members apart.
enum
A closed set of named values, validated by the server rather than by the resolver.
A closed set of named values is validated by the server.
Validation happens before the resolver runs, so an invalid value never reaches it.
Adding a value can break a client that exhaustively matches the old set.
scalar
A leaf value. Custom scalars such as DateTime and JSON carry their own parse and serialise logic.
Leaf values carry their own parse and serialise logic when custom.
A custom scalar is where format validation belongs, not in every resolver.
Clients without the scalar's meaning receive it as an opaque value.
directive
Declarative behaviour attached to a schema element or a query node.
Declarative behaviour attaches to a schema element or a query node.
It expresses cross-cutting concerns without changing the field's type.
A directive the server does not implement changes nothing at execution.
extend
Adds fields to an existing type, which is how schemas are composed across modules.
Fields are added to an existing type, which is how modules compose a schema.
Composition lets one type be assembled from several files.
Extending a type that is never defined fails at composition.
schema
Names the root operation types when they are not called Query, Mutation and Subscription.
The root operation types are named when they are not the defaults.
It is only needed where the roots carry other names.
Only the types named here are reachable as entry points.
Type system
Non-null (!)
The field can never be null. Adding one is a breaking change for clients; removing one is not.
The field can never be null, and the client's type reflects that.
Adding one is breaking for clients; removing one is not.
A null from the resolver becomes an error that propagates upward.
List ([T])
A list of a type. [T!]! means a non-null list of non-null items — the usual intent.
A list of a type, where nullability applies to the list and its items separately.
The usual intent is a non-null list of non-null items.
A null item in a non-null item list nulls the whole list.
Arguments
Typed parameters on a field, with defaults and validation before the resolver runs.
Typed parameters are validated, with defaults applied, before the resolver runs.
Validation ahead of the resolver keeps that check out of every resolver.
A default is applied when the argument is absent, not when it is null.
Variables
$name placeholders sent alongside the document, so queries stay static and cacheable.
Placeholders sent beside the document keep the query text static.
A static document is what makes caching and persisting possible.
Interpolating values into the query string instead defeats both.
Fragments
Named reusable selection sets — the unit colocation and client codegen are built on.
Named reusable selection sets are the unit colocation and codegen build on.
A component's data requirement travels with the component.
A fragment must be spread on a type that actually has its fields.
Inline fragments
... on Type selects fields that only apply to one member of an interface or union.
A selection applies to only one member of an interface or union.
It is the only way to read fields the abstract type does not declare.
Omitting a member means that case returns nothing selectable.
Aliases
Renames a field in the response, which is how the same field is queried twice with different arguments.
A field is renamed in the response so it can be queried twice with different arguments.
Without one the second selection collides with the first.
The alias is the response key, which is what the client reads.
__typename
The meta-field naming the concrete type. Clients need it for cache normalisation and union handling.
The meta-field names the concrete type of an object.
Cache normalisation and union handling both depend on it.
Clients usually request it automatically, which changes the document sent.
Introspection
The schema querying itself. Disable it in production if the schema is not meant to be public.
The schema can be queried through the schema itself.
Tooling, explorers and codegen are all built on it.
Leaving it open publishes the whole shape of a private API.
@deprecated
Marks a field or enum value as obsolete, with a reason, without breaking existing clients.
A field or enum value is marked obsolete with a stated reason.
It warns clients without removing anything they still call.
Deprecation communicates intent; it does not enforce migration.
@include / @skip
Conditionally includes a selection based on a variable.
A selection is included conditionally based on a variable.
The document stays static while the response varies.
Different variables produce different response shapes from one query.
@defer / @stream
Delivers slow fields and long lists incrementally rather than holding the whole response.
Slow fields and long lists are delivered incrementally.
The fast part of a response is not held behind the slow part.
The client and the transport must both support multipart delivery.
@oneOf
An input object where exactly one field may be set — a tagged union for inputs.
Exactly one field of the input object may be set.
It expresses a tagged union for inputs, which the type system otherwise lacks.
The constraint is validated by the server, not by convention.
Execution
Resolvers
(parent, args, context, info) functions that produce a field's value. The default reads the property.
A field's value comes from a function of parent, arguments, context and info.
The default resolver simply reads the property of the same name.
Every field can resolve, which is what makes N+1 so easy to create.
Context
Per-request state — the authenticated user, loaders and database handles — built once per operation.
Per-request state such as the user, loaders and handles is built once per operation.
Sharing it per request is what makes batching within an operation possible.
Context reused across requests leaks one user's data into another's.
The N+1 problem
Field resolvers firing one query per parent. The defining performance problem of GraphQL.
A field resolver firing one query per parent is the defining performance failure here.
It appears only once a list is queried, so it hides in development.
The cost scales with result size rather than with query complexity.
DataLoader
Batches and caches loads within a request, which is the standard answer to N+1.
Loads are batched and cached within one request.
Batching turns many parent lookups into a single query.
A loader shared between requests caches one user's data for another.
Field middleware
Wraps every resolver for tracing, authorisation or masking without touching each one.
Every resolver is wrapped for tracing, authorisation or masking.
One wrapper is what avoids repeating a check in each resolver.
It runs on every field, so its cost multiplies with query size.
Error handling
Errors go in errors with a partial data. Use extensions.code rather than parsing messages.
Errors arrive in an errors array alongside partial data.
A code in extensions is what clients should branch on, not the message.
A partial success still returns 200, so status alone says nothing.
Nullability and errors
A resolver error on a non-null field propagates up to the nearest nullable parent.
An error on a non-null field propagates to the nearest nullable parent.
Over-using non-null turns a single failure into a mostly empty response.
A nullable field is what lets one failure stay local.
Execution order
Query fields may resolve in parallel; mutation fields at the top level run one after another.
Query fields may resolve in parallel; top-level mutation fields run in series.
Serial mutations make two writes in one document predictable.
Relying on ordering between query fields is relying on nothing.
Server libraries
graphql-js
The reference implementation: parser, validator and executor every JS server builds on.
The reference parser, validator and executor every JavaScript server builds on.
Its behaviour is the practical definition of the specification here.
It provides execution only; transport and caching are someone else's.
@apollo/server
The widely deployed server, with plugins, caching, metrics and landing pages.
The widely deployed server with plugins, caching, metrics and a landing page.
Its plugin lifecycle is where cross-cutting concerns attach.
Its ecosystem assumptions are heavier than a minimal handler's.
graphql-yoga
A batteries-included, spec-compliant server that runs on any JavaScript runtime.
A spec-compliant, batteries-included server that runs on any JavaScript runtime.
Running on web-standard requests is what makes it portable across runtimes.
Its defaults are opinionated, which is the trade for the batteries.
mercurius
The Fastify GraphQL adapter, with JIT compilation and subscriptions.
The Fastify adapter, with JIT compilation and subscriptions.
Compiling queries rewards a workload that repeats the same documents.
It is tied to Fastify rather than being framework-neutral.
graphql-http
A minimal, strictly spec-compliant HTTP handler with no framework opinion.
A minimal handler that is strict about the HTTP specification and has no framework opinion.
Strictness is what makes it a good conformance baseline.
Everything beyond execution is left for you to add.
pothos
A code-first schema builder whose types are inferred, not generated.
A code-first builder whose types are inferred rather than generated.
No generation step means the schema types cannot fall out of date.
The schema is a build artifact, so it must be emitted for other tools.
nexus
Code-first schema construction with generated types and a plugin system.
Code-first construction with generated types and a plugin system.
Generation is what supplies the types the builder cannot infer.
The generated output has to be regenerated and committed deliberately.
type-graphql
Decorator-driven schemas from TypeScript classes, the style Nest adopts.
Schemas are built from decorated TypeScript classes.
One class can describe the type and carry its validation.
It relies on decorators and metadata, which constrains the build setup.
gqlgen
Schema-first Go server generating strongly typed resolver stubs.
A schema-first Go server that generates typed resolver stubs.
Regenerating after a schema change surfaces unimplemented resolvers at compile time.
Generated code is committed, so the diff is part of review.
strawberry-graphql
Python schemas from dataclass-style type annotations, async throughout.
Python schemas come from dataclass-style annotations and are async throughout.
Annotations mean the type and the schema are declared once.
It expects an async stack end to end.
graphene
The long-established Python library, with Django and SQLAlchemy integrations.
The long-established Python library with Django and SQLAlchemy integrations.
Its integrations map an existing model layer onto a schema quickly.
Its API has shifted across major versions, so examples date badly.
async-graphql
A fast, fully featured Rust implementation.
A fast, fully featured Rust implementation.
Execution cost is low enough to change what is worth resolving per field.
It expects Rust's async ecosystem around it.
graphql-java
The JVM implementation, with Spring for GraphQL layered on top.
The JVM implementation, with Spring for GraphQL layered above it.
It is the substrate the JVM ecosystem's higher-level tools build on.
Used directly it is deliberately low level.
hot-chocolate
The .NET server, with filtering, sorting and projections built in.
The .NET server, with filtering, sorting and projections built in.
Projections push selection down into the database query.
Those conveniences expose the data model unless they are constrained.
Client libraries
@apollo/client
A normalised cache, hooks, optimistic updates and local state in one client.
A normalised cache, hooks, optimistic updates and local state in one package.
Normalisation is what makes one mutation result update every view.
It is the largest of the clients, which matters to a bundle.
urql
A smaller client with document caching by default and Graphcache when normalisation is needed.
A smaller client with document caching by default and Graphcache when normalising.
Document caching is simpler and enough for many applications.
Moving to Graphcache changes invalidation across the application.
graphql-request
A minimal fetch wrapper for scripts and server-to-server calls, with no cache.
A minimal fetch wrapper for scripts and server-to-server calls.
No cache is the point: each call is one request.
Nothing is deduplicated, so repeated calls repeat the work.
relay
Compiler-driven, fragment-first data fetching with strict schema conventions and strong guarantees.
Compiler-driven, fragment-first fetching with strict schema conventions.
The compiler is what turns colocated fragments into one optimal query.
Its conventions are requirements, not suggestions.
@tanstack/react-query + codegen
Generic async state with generated typed hooks, for teams that want no GraphQL-specific cache.
Generic async state with generated typed hooks and no GraphQL-specific cache.
One cache covers GraphQL and everything else the application fetches.
Without normalisation, entity updates are invalidations rather than patches.
Normalised cache
Stores entities by id so one mutation result updates every view showing that entity.
Entities are stored by id, so one result updates every view showing them.
It is what makes a mutation update a list and a detail view at once.
An entity without a stable id cannot be normalised and is duplicated.
Optimistic updates
Writes the expected result immediately and rolls back if the mutation fails.
The expected result is written immediately and rolled back on failure.
The interface responds at input speed rather than at network speed.
A rollback that leaves other derived state behind is visible to the user.
Persisted queries
Sends a hash instead of the document, cutting payload size and allowing an operation allowlist.
A hash is sent instead of the document, cutting payload and enabling an allowlist.
An allowlist removes arbitrary queries as a category of risk.
Every client build must be registered before it can run.
Federation & gateway
Apollo Federation
Composes independently deployed subgraphs into one supergraph.
Independently deployed subgraphs compose into one supergraph.
Teams own and deploy their subgraph without a shared release.
Composition can fail on changes that each subgraph considers valid.
@key
Declares the fields identifying an entity across subgraphs.
The fields identifying an entity across subgraphs are declared.
A shared identity is what lets one type span several services.
A key that is not genuinely unique breaks resolution silently.
@external / @requires / @provides
Describe fields owned elsewhere and the data a resolver needs or can supply.
Fields owned elsewhere are described, along with what a resolver needs or can supply.
Declaring a dependency lets the planner fetch it before the resolver runs.
@requires adds a fetch, so it is a cost as well as a convenience.
@shareable
Allows more than one subgraph to resolve the same field.
More than one subgraph may resolve the same field.
It allows deliberate overlap that composition would otherwise reject.
Two subgraphs disagreeing on the value is not detected at composition.
Entity resolution
__resolveReference lets the gateway fetch an entity from its owning subgraph.
A reference resolver fetches an entity from its owning subgraph.
It is the hook the gateway calls when following a key.
It receives only the key fields, not the whole parent.
Apollo Router
The Rust gateway: query planning, caching and traffic shaping.
The Rust gateway handling query planning, caching and traffic shaping.
Planning is what turns one client query into minimal subgraph fetches.
It is one more deployed component to operate and observe.
GraphQL Mesh
Presents REST, gRPC and databases as a single GraphQL schema.
REST, gRPC and databases are presented as one GraphQL schema.
An existing service is exposed without being rewritten first.
The generated schema reflects the source's shape rather than a designed graph.
Schema stitching
Merges schemas at the gateway without federation directives. Simpler, and less able to evolve.
Schemas merge at the gateway without federation directives.
It is simpler to start with and needs no subgraph cooperation.
Merge rules live at the gateway, so ownership stays centralised.
Code generation
graphql-codegen
Generates types, hooks and SDKs from a schema and your documents.
Types, hooks and SDKs are generated from the schema and your documents.
Generation is what keeps client types honest against the schema.
Output is only as current as the last run against the real schema.
client-preset
The current codegen approach: a typed graphql() function with fragment masking.
A typed graphql function with fragment masking is the current approach.
One preset replaces the stack of plugins earlier setups assembled.
Masking changes what a component may read, which existing code must follow.
Fragment masking
A component may only read the fields its own fragment declared, enforced by the types.
A component may read only the fields its own fragment declared.
It is enforced by the types, so an accidental read fails to compile.
Data must be unmasked deliberately where a component genuinely needs it.
Schema linting
graphql-eslint and rule packs catch naming, nullability and deprecation mistakes in review.
Naming, nullability and deprecation mistakes are caught in review.
Review is the last point before a convention becomes permanent.
Rules have to match the team's conventions or they are noise.
Schema registry
Version-controlled schemas with composition checks — Apollo Studio, Hive or GraphQL Inspector.
Schemas are version controlled with composition checks.
A check before publish is what stops a subgraph breaking the supergraph.
It only knows about schemas that are actually published to it.
Breaking change detection
Compares a proposed schema against the last published one, and against real traffic.
A proposed schema is compared against the published one and against real traffic.
Traffic data distinguishes a theoretical break from one that affects a client.
A field nothing queried yesterday may still be queried tomorrow.
Security & performance
Query depth limiting
Rejects deeply nested queries before execution, since the graph may be cyclic.
Deeply nested queries are rejected before execution.
A cyclic graph makes unbounded depth trivially reachable.
Depth alone does not bound a shallow query over a huge list.
Query complexity analysis
Scores a query by estimated cost and rejects it over a budget.
A query is scored by estimated cost and refused over a budget.
Cost accounts for list sizes, which depth alone ignores.
The estimate is only as good as the per-field weights given.
Operation allowlisting
Only pre-registered documents may run, which removes arbitrary query risk entirely.
Only pre-registered documents may run.
It removes arbitrary queries as a risk entirely.
Every client release must be registered or it stops working.
Field-level authorisation
Checks permission where the data is resolved, not at the HTTP edge.
Permission is checked where the data resolves, not at the HTTP edge.
One request can reach many resources, so an edge check is the wrong place.
A field reachable by two paths must be checked on both.
Rate limiting
Per-field or per-complexity limits rather than per-request, since one request can do anything.
Limits are applied per field or per complexity rather than per request.
One request can do arbitrary work, so counting requests measures nothing.
The counter must be shared across instances to mean anything.
Response caching
Per-field cache hints combined into a whole-response TTL.
Per-field cache hints combine into one time to live for the response.
The most restrictive hint is what bounds the whole response.
A personalised field makes the entire response uncacheable.
Automatic persisted queries
Hash-first requests that fall back to registering the document, cutting upload size.
A hash-first request falls back to registering the document.
Upload size drops without a build-time registration step.
Unlike an allowlist, it still permits new documents to run.
Batching
Several operations in one HTTP request. Bound the batch size or it becomes an amplification vector.
Several operations travel in one HTTP request.
It reduces round trips for a client issuing many small queries.
An unbounded batch is an amplification vector.
Testing & tooling
graphql-tools
Schema construction, mocking, stitching and transforms.
Schema construction, mocking, stitching and transforms in one toolkit.
Transforms are what let an existing schema be reshaped at a gateway.
It is a library of parts rather than a server.
Mocked schemas
Auto-generated resolvers from types, so a frontend can build before the backend exists.
Resolvers are generated from types so a frontend can build before the backend exists.
The schema is enough of a contract to work against on its own.
Mock data that no real resolver would return hides integration problems.
MockedProvider
Apollo's test harness for components, matching queries to canned results.
Components are tested against canned results matched to queries.
It exercises the component's real data flow without a server.
A mock that does not match the query exactly yields an error instead.
graphiql
The in-browser IDE: explorer, docs, autocomplete and history.
The in-browser explorer with docs, autocomplete and history.
It is driven by introspection, so it stays current with the schema.
Exposing it publicly exposes the schema with it.
Apollo Sandbox
A hosted explorer with schema search and operation collections.
A hosted explorer with schema search and saved operation collections.
Collections are what make useful queries shareable across a team.
It reaches your endpoint, so access and CORS have to allow it.
Tracing
Per-resolver timing, usually exported through OpenTelemetry, to find the slow field.
Per-resolver timing is exported, usually through OpenTelemetry.
Resolver-level detail is what identifies the one slow field.
Tracing every field on every request is itself a cost.
Ask follow-up questions on any topic in your dashboard.