GraphQL

Schema Design, Naming, And Governance

Design For Behavior, Not For Storage

The strongest schema-design heuristic: model what clients do, not how you store data.

  • Don't mirror tables. A join table like artist_genre is an implementation detail; the schema should say Artist.genres: [Genre!]!. Database-shaped schemas leak every migration into the API contract.
  • Don't mirror one screen either. A HomePageData type serves exactly one screen for exactly one release. Model the domain's entities and let screens compose queries — that composition ability is why you chose GraphQL.
  • Prefer specific fields over generic ones. publishAlbum beats updateAlbum(status: ...); Track.durationSeconds: Int! beats Track.metadata: JSON. Every stringly-typed or catch-all field is a place where the type system — your validation, docs, and tooling — goes blind.
  • Expose objects, not foreign keys. Review.album: Album! beats Review.albumId: ID! — the object field lets clients traverse in one query, while a raw id forces a second lookup and reintroduces REST's stitching problem. Keep id fields on the objects themselves; edges between objects should be object-typed.

Naming Conventions

Names are the API's UX, and GraphQL's ecosystem has settled conventions worth following exactly:

  • Types: PascalCase nouns (Album, ReviewConnection). Input types suffixed Input; mutation payloads suffixed Payload.
  • Fields and arguments: camelCase. Booleans read as assertions (isExplicit, hasLyrics). List fields are plural nouns.
  • Enum values: SCREAMING_SNAKE_CASE.
  • Mutations: verb-first, verbObject (postReview, archiveTrack) — sorting alphabetically then groups actions by verb rather than clustering all album* names.
  • Say units in the name when a scalar could mislead: durationSeconds, not duration. A rename later is a breaking change; the extra characters now are free.
  • Be consistent above all. creator on one type and author on its sibling costs every client developer a lookup forever. Keep a small glossary of blessed domain terms and reuse them.

Nullability As A Contract Decision

The schema lesson gave the mechanical rule (non-null failures blow up their parents); the design-level rule is: non-null is a promise you must keep for the field's whole life. Marking Album.label: Label! promises every album, past and future, always has a label — including after the acquisitions data migration next year. Loosening ! later is a breaking change for clients that relied on it. When history offers no strong guarantee, start nullable; tightening nullable → non-null is the safe direction. For arguments and input fields it flips: required (non-null) inputs are the restrictive choice, and making a required input optional is the safe evolution.

Evolving Without Breaking

The additive rules — what you may always do:

  • add types, add fields, add optional arguments, add optional input fields,
  • add values to enums and members to unions only if clients were told to expect unknowns (a good reason clients should always handle a default case),
  • deprecate anything.

What you may never do silently: remove or rename anything reachable, change a field's type, make an output field nullable, make an input required. The workflow for those is: add the replacement, mark the old one @deprecated(reason: "...") pointing at it, watch field-level usage telemetry (per-client field usage logging is the governance tool that makes everything else possible), contact the laggards, and remove only when usage hits zero — which on public APIs may be never. This is why GraphQL APIs rarely need /v2: the schema evolves field by field instead of wholesale.

CI should enforce this mechanically: dump the schema each build and run a breaking-change diff against the deployed version, failing the pipeline on anything in the forbidden list (schema-diff tools exist off the shelf; the introspection lesson covered producing the dumps).

Governance: A Schema Is A Shared Asset

Once several teams contribute to one graph, the schema needs the same care as a public codebase:

  • Schema review. Changes to the SDL get reviewed by people wearing the API consumer hat — checking naming consistency, nullability promises, pagination shape, and error conventions, not just "does it work". A short written checklist (yours will resemble this track's lessons) keeps reviews consistent, and a small group of experienced reviewers keeps the graph sounding like one author.
  • Ownership. Every type and root field has an owning team recorded (a registry file or directives in the SDL). Owners answer design questions, review changes touching their types, and own the deprecation shepherding for their fields. Unowned schema is where inconsistency and abandoned experiments accumulate. Ownership models sit on a spectrum: centralized (one API team approves everything — coherent, but a bottleneck as contributors multiply), federated (each team owns and ships its slice, with composition tooling enforcing the boundaries — the federation lesson's architecture is this model made structural), and hybrid (teams own their types; a small platform group owns cross-cutting conventions and reviews only those). Most organizations drift from centralized toward hybrid as the graph grows; choosing the model explicitly beats discovering it during a dispute.
  • Linting. Naming and shape conventions are mechanically checkable — schema linters can enforce boolean prefixes, Input/Payload suffixes, connection shapes, and description coverage in the same CI run as the breaking-change diff, so reviews spend attention on design rather than style.
  • A design guide. Write the conventions down — naming glossary, mutation payload pattern, error strategy, pagination defaults — so new contributors copy the house style instead of inventing dialects. Most large GraphQL shops publish exactly such a document internally.
  • Deprecation policy. Agree in advance how long deprecated fields live and how consumers are notified, so removals are routine instead of negotiations.

None of this needs heavyweight process at small scale — a review checklist and a CI schema diff are an afternoon's setup — but retrofitting coherence onto a hundred-type graph that grew without it is genuinely hard. Start the habits early.

What To Check

Before moving on, make sure you can:

  • critique a schema that mirrors database tables or a single screen, and reshape it around the domain
  • apply the naming conventions, including payload/input suffixes and units in names
  • explain which nullability direction is the safe evolution for outputs and for inputs, and why they differ
  • list the always-safe additive changes and the never-silent breaking ones
  • describe the deprecate → measure → remove workflow and the telemetry it depends on
  • sketch a lightweight governance setup: review checklist, ownership registry, CI schema diff

What You Should Be Able To Do

After this lesson, you should be able to act as a schema reviewer: read a proposed SDL change and flag naming drift, storage leakage, rash non-null promises, and breaking changes — and set up the small amount of process (design guide, ownership, CI diffing, deprecation policy) that keeps a multi-team graph coherent for years.