skip to content

In GraphQL, what does a client see when many services sit behind one endpoint?

level: juniorimportance: must knowfreq 57%

answer

  1. The caller's view does not change
  2. One URL, one schema, one response
  3. Many owners, a single selection set
  4. Nothing attributes a field to a service

basics

~20 s

One schema and one endpoint. The client sends a single operation and gets one response, and nothing in that response says which service produced which field. The split across services is a server-side arrangement the caller never sees.

solid answer

~50 s

The client-facing contract does not change with the number of services involved: one URL, one schema it can introspect, one executable document per request, one response object back. A document over a fleet telematics graph can select a vehicle's registration, its last trip and its open maintenance orders in one selection set even though three teams' services hold those three facts, and the client writes it as if a single server owned all of them. What the client cannot see is which service answered which field — the response envelope has no slot for that attribution, and no meta-field reports it. That transparency is the point, and it is also the constraint: whatever machinery joins the parts, the schema the client reads has to look like one coherent type system rather than several APIs stapled together.

code

graphql · 14 lines
graphql
query VehicleOverview($vin: ID!) {
  vehicle(vin: $vin) {
    registration
    lastTrip {
      startedAt
      distanceKm
    }
    openMaintenanceOrders {
      id
      raisedAt
      severity
    }
  }
}

go deeper

for a junior

Be ready to say that the caller sees one URL and one schema no matter how many services are behind it, and that nothing in the response identifies a backend. Interviewers ask this to check you separate the client contract from the server arrangement.

for a middle

Explain the mechanics of the envelope: data, errors with a response path, and a free-form extensions. Be able to say why one graph is one namespace and why that forces two teams to reconcile a duplicated type name.

for a senior

Show that you know what the invariant does not cover. Per-field latency and per-field failure differ sharply once owners are distributed, so nullability and error budgets have to be reasoned about field by field rather than per endpoint.

for a principal

Own the consequence of the single namespace: naming, deprecation and conflict arbitration become organisational policy the moment more than one team contributes. Decide early who adjudicates a collision and whether the build fails closed on one.

## The thing that does not change Whatever a GraphQL API looks like on the inside, the client-facing contract is fixed: **one URL, one schema, one document per request, one response**. A caller points a request at the endpoint, sends an executable document, and receives a map containing `data` and, when something went wrong, `errors`. Nowhere in that exchange is there a place to record *which server produced this*. That is what "one graph" means, and it is worth being precise about it, because the phrase gets used loosely. It is not a claim about deployment. It is a claim about the type system the client sees: a single set of types, a single `Query` root type, and every declared field selectable from the same document as every other. One endpoint does not imply one server, and one schema does not imply one repository. It only implies that the caller is presented with one coherent graph. ## What the invariant buys the caller Take a fleet telematics graph. A vehicle's registration and VIN live in a vehicles service. Trips and their distances live in a trips service that ingests device telemetry. Open maintenance orders live in a maintenance service that a different team ships on its own cadence. An operations dashboard needs all three for one vehicle. Against three separate HTTP APIs that is three round trips, three authentication integrations, three error paths, and joining code in the browser. Against one graph it is one document with one nested selection set, and the joining is somebody else's problem. The caller never learns that three teams and three datastores were involved. It also never learns the order those services were consulted in, whether they were consulted in parallel, or whether one of them answered from a cache. ## What the response does and does not carry A GraphQL response is a map with three reserved entries: `data`, `errors` and `extensions`. An entry in `errors` carries a `path` — the position of the failing field in the *response*, such as `["vehicle", "openMaintenanceOrders"]`. That path locates a field in the caller's own document. It does not name a service, and it is not meant to. `extensions` is a free-form map that the specification deliberately leaves to implementations. An operator may put tracing or diagnostic material there, but there is no standard key meaning "which backend answered", and a client must not be written as though one exists. There is likewise no meta-field to ask with. GraphQL reserves the double-underscore names for introspection — `__typename`, `__schema`, `__type` — and none of them reports a backing service. If a client can tell which service answered a field, it is because an operator chose to leak that, not because the protocol offers it. ## Why presenting one graph is harder than it looks One graph means one **namespace**. Two teams cannot both define `Trip` to mean different things, because the caller sees exactly one `Trip`. Whichever route an organisation takes to combining schemas, that reconciliation has to happen somewhere: either one team authors the whole schema, or a merging step refuses to produce a graph at all until the two definitions agree. This is the first real cost of the arrangement, and it is organisational rather than technical — it is a conversation between teams, expressed as a build failure. The invariant also survives failure, but only in a particular way. If the maintenance service is unreachable, the caller does not receive a transport error saying so. It receives a normal response whose `data` has a hole where those fields were, plus an `errors` entry with a path pointing at them. From the caller's side that is indistinguishable from a single server that failed on one field, which is exactly the intent. ## What the invariant does not promise Transparency about topology is not transparency about behaviour. A field backed by a distant service is slower than a field read from the same process, and a query that touches four owners has four independent ways to fail. The caller cannot see the seams, but it can feel them — which is why latency and error budgets for a multi-owner graph have to be reasoned about per field rather than per endpoint. And the invariant says nothing about how the schema was built. The schema a client introspects may have been authored in one file by one small team, or assembled from parts published by nine of them. Which of those it is has enormous consequences for the people who build and operate the graph, and none at all for the people who query it. Holding those two facts apart — the caller's view, and the producers' arrangement — is the whole subject.

  • If the caller cannot tell which service answered, how do two teams avoid colliding on a type name?
    They cannot avoid it by namespacing, because the graph is one namespace — the caller sees exactly one `Trip`. Either one team authors the definition, or the step that merges the schemas refuses to produce a graph until the two definitions agree. In practice that means a naming convention and an arbitration path, backed by a build that fails closed rather than silently picking a winner.
  • Is there anything in a GraphQL response that names the backend service behind a field?
    No. The response carries `data`, `errors` and `extensions`. An error's `path` points at a field in the response, not at a server. `extensions` is a free-form map the specification leaves to implementations, so an operator may put tracing there, but there is no standard key for backend attribution and clients should not be written to expect one.
  • Does one endpoint mean the caller is insulated from how many services are behind it?
    Insulated in shape, not in behaviour. The document and the response look the same either way, but a field served from a distant process is slower and adds an independent failure mode. So a client is right to treat per-field latency and per-field nullability as real concerns even though the topology is invisible to it.

A restaurant menu does not tell the diner which farm grew each vegetable. One menu, one order, one plate — the supply chain is the kitchen's business.

saying these in an interview costs you the question

  • Thinks the client sends one request per backing service
  • Expects the response to say which service answered
  • Believes each backing service exposes its own endpoint to clients
  • Assumes two owners may define the same type differently
  • Thinks one endpoint must mean one server
  • Says a failed backend produces a transport-level error for the client

context