Apollo
02 / 02

Server-Side: Schema, Resolvers & the N+1 Problem

Server-Side: Schema, Resolvers & the N+1 Problem

Schema-First with SDL

type User {
  id: ID!
  name: String!
  posts: [Post!]!
}

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

The schema is a language-agnostic contract describing exactly what the API supports — frontend and backend teams can agree on it before resolvers are fully implemented.

Resolvers

const resolvers = {
  Query: {
    user: (_, { id }) => db.users.findById(id),
  },
  User: {
    posts: (user) => db.posts.findByAuthorId(user.id),
  },
};

Each field can have its own resolver — querying user.posts triggers the User.posts resolver after the top-level Query.user resolver runs.

The N+1 Problem & DataLoader

// naive: fetching 10 posts' authors triggers 10 separate queries
// DataLoader batches them into one
const userLoader = new DataLoader(async (ids) => {
  const users = await db.users.findByIds(ids);
  return ids.map(id => users.find(u => u.id === id));
});

GraphQL's nested-field flexibility makes N+1 easy to introduce by accident — DataLoader collects individual lookups within a request into one batched query.

Subscriptions, Polling & Choosing GraphQL

Subscriptions push real-time updates over a persistent connection (WebSockets); pollInterval on useQuery is a simpler periodic-refresh alternative when true real-time isn't needed. GraphQL shines when different views/clients need meaningfully different data shapes from the same underlying data — for simpler, uniform APIs, REST's lower implementation complexity may not be worth trading away.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free