Skip to content
masterclassBuddyTech Buzz5 fresh blogs · 5:00 AM IST

Technology handbook

GraphQL — Schema, Servers, Clients & Tooling

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.

Start learning