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