Part 3 · 2 chapters · ~12 min

GraphQL on the Server

Schemas and resolvers, the N+1 problem and DataLoader, query depth and cost limits, persisted queries, authorisation per field, errors in GraphQL responses, federation across services, caching difficulties, and when GraphQL is the right choice.

7

Resolvers and batching

code
// DataLoader: batch beneficiary lookups made during one request
import DataLoader from 'dataloader';
export const loaders = () => ({
  beneficiary: new DataLoader(async (ids: readonly string[]) => {
    const rows = await db.query('SELECT * FROM beneficiaries WHERE id = ANY($1)', [ids]);
    const byId = new Map(rows.map(r => [r.id, r]));
    return ids.map(id => byId.get(id) ?? null);           // same order as requested
  }),
});
const resolvers = { Transfer: { beneficiary: (t, _a, ctx) => ctx.loaders.beneficiary.load(t.beneficiaryId) } };
GRAPHQL ON THE SERVER
one schema, resolvers per field, and the N+1 problem DataLoader solves
queryaccount { transfers { beneficiary { name } } }executorwalks the selection setaccount resolver1 querytransfers resolver1 querybeneficiary resolverN queries!DataLoaderbatch per tick
swipe the figure sideways, or tap expand for full screen
1/5
schema and selection
Clients ask for exactly the fields they need; the executor walks the selection set and calls a resolver for each field.
clients choose fieldsa resolver per field
8

Authorisation, errors and federation

topicpractice
authorisationin the business layer the resolvers call, not in the schema; every object fetched by id is checked (BOLA again)
errorspartial data plus an errors array; use typed error unions (TransferResult = Transfer | InsufficientFunds) for expected failures
federationApollo Federation or GraphQL Mesh compose subgraphs owned by different teams into one graph; schema changes need composition checks in CI
cachingHTTP caching is hard (POST); use persisted queries over GET, response caching per field, or client caches