Konrad Kowalski (rootsher)Principal Platform & Reliability Architect011110000101101100111000100011100100010011111011

GraphQL: Letting the Client Define the Shape of Data

date
category
Backend
also in
Frontend · System Design
reading
2 min / 373 words

REST usually ties the response to a resource.

RPC ties it to an operation.

GraphQL ties it to the client's query.

graphql
query {
  user(id: 42) {
    name
    orders {
      id
      total
    }
  }
}

The client defines the shape of the data it needs.

One endpoint, many queries

A typical request looks like this:

http
POST /graphql
Content-Type: application/json

The body contains a GraphQL document.

HTTP no longer says:

text
which resource are we fetching?

That is described by the GraphQL layer.

text
HTTP
  |
  v
GraphQL document
  |
  v
schema
  |
  v
execution

The schema is the contract

For example:

graphql
type User {
  id: ID!
  name: String!
  orders: [Order!]!
}

type Query {
  user(id: ID!): User
}

The schema defines a graph of types and the operations available to the client.

A query is validated against the schema before execution.

That gives a very strong contract and excellent tooling.

Resolvers

Every field can be resolved by backend code.

Simplified:

text
Query.user
  |
  v
User.orders
  |
  v
Order.total

That allows composing data from many sources without exposing that structure to the client.

But it leads to one of the classic GraphQL problems.

N+1

Suppose:

graphql
query {
  users {
    orders {
      id
    }
  }
}

A naive implementation may perform:

text
1 query -> users
N queries -> orders for each user

That is N+1.

GraphQL does not cause it automatically, but its resolver model exposes such a problem very easily.

The typical solution is batching and request-scoped caching, for example through a DataLoader-like pattern.

Flexibility has a price

In REST the backend knows the cost of an endpoint well in advance.

In GraphQL the client can build many different query trees.

So there are things that have to be controlled:

  • depth,
  • complexity,
  • pagination,
  • expensive fields,
  • authorization per field,
  • query limits.

The API becomes more flexible, but its execution becomes less predictable.

Caching is harder too

The HTTP cache understands this well:

http
GET /users/42

It copes far worse with:

http
POST /graphql

where the same URL can represent thousands of different queries.

GraphQL usually moves a bigger part of caching into the client application layer.

The client can normalize data by identifiers:

text
User:42
Order:17
Order:18

That is powerful, but again: more flexibility means more mechanism on the application side.

REST, RPC or GraphQL?

There is no hierarchy:

text
REST < RPC < GraphQL

These are different models.

text
REST
-> resources and HTTP semantics

RPC
-> operations and a contract of procedures

GraphQL
-> a data graph and a query language

All of them can use the same HTTP transport and all of them can be called with fetch().

That is the most important conclusion of the whole series:

text
Fetch        = browser API
HTTP         = protocol
REST / RPC   = API architecture
GraphQL      = application query protocol

These layers solve different problems.

Mixing them leads to arguments like "GraphQL vs HTTP" or "fetch vs REST", which compare things from different levels of abstraction.

Only once those layers are separated can frontend to backend communication be designed sensibly.