GraphQL is the API query language that powers some of the most data-heavy mobile and web apps in India, including CRED's app, Razorpay's developer dashboard, and a growing number of Indian product companies migrating from REST. This guide covers GraphQL schema design, resolvers, the N+1 performance problem, and subscriptions for real-time data.
GraphQL vs REST: when to use each
GraphQL vs REST comparison:
1. REST (Representational State Transfer): - Multiple endpoints, each returning a fixed response shape (GET /users, GET /users/123/posts) - Simple; well-understood; native HTTP caching; widely supported by tools and proxies - Over-fetching: GET /users returns all fields even if the client only needs the name and email - Under-fetching: GET /users does not include posts; a second request is needed - Best for: simple CRUD APIs, public APIs, or APIs consumed by non-browser clients
2. GraphQL: - Single endpoint (typically /graphql); client specifies exactly what data it needs in the query; server returns exactly that - Strongly typed schema (SDL: Schema Definition Language): all types, queries, mutations, and subscriptions are defined in the schema; clients know exactly what data is available and in what shape - No over-fetching or under-fetching: the client gets exactly what it requested - Real-time with subscriptions: GraphQL subscriptions allow the server to push data to the client via WebSocket when events occur - Best for: mobile apps (bandwidth-sensitive), complex data graphs with many entity relationships, APIs serving multiple different clients (web, mobile, smart TV) with different data needs
3. When to choose REST over GraphQL: - Simple APIs with few entity types - APIs that benefit from HTTP caching at the network layer - Teams unfamiliar with GraphQL (steep learning curve for schema design and N+1 prevention) - File upload APIs (GraphQL file upload is possible but awkward)
In practice: many Indian product companies use a hybrid approach: REST for simple CRUD and file handling, GraphQL for complex data-fetching surfaces (app home feed, dashboards with many related entities).
GraphQL schema design and resolvers
GraphQL schema design and resolver concepts:
1. Schema Definition Language (SDL): GraphQL schemas are defined in SDL: ``` type User { id: ID! name: String! email: String! posts: [Post!]! }
type Post { id: ID! title: String! body: String! author: User! }
type Query { user(id: ID!): User users: [User!]! }
type Mutation { createUser(name: String!, email: String!): User! } ``` ! means non-nullable (the field is always present). [Post!]! means a non-nullable list of non-nullable Posts.
2. Resolvers: A resolver is a function that resolves a GraphQL field. Every field in a GraphQL schema has a resolver (default: return the field with the same name from the parent object). Custom resolvers: needed for computed fields, database queries, or external API calls.
Resolver arguments: (parent, args, context, info):
- parent: the resolved value of the parent field (e.g., for posts on a User, parent is the User object)
- args: arguments from the query (e.g., { id: '123' } for user(id: '123'))
- context: request-scoped shared state (database connection, authenticated user, DataLoader instances)
- info: metadata about the query (field name, return type, path)
3. Mutations: Mutations are write operations. They follow the same resolver pattern as queries but are used for create, update, and delete operations. Convention: return the mutated entity so the client can update its cache.
N+1 problem and DataLoader
The N+1 problem is the most important GraphQL performance topic:
1. What causes the N+1 problem: A query fetches a list of 100 users and their posts: ```graphql query { users { name posts { title } } } ``` Without DataLoader: the users resolver makes 1 query for all users, then the posts resolver on each User makes a separate database query for that user's posts, resulting in 1 + 100 = 101 database queries.
2. DataLoader solution: DataLoader batches multiple requests in the same event loop tick into a single request: - Batch function: receives an array of keys (user IDs) and returns an array of values (posts arrays) - DataLoader collects all the user IDs requested in the current event tick, then calls the batch function once: SELECT * FROM posts WHERE user_id IN (1, 2, 3, ..., 100) - Result: 2 queries total (1 for users, 1 for all posts) - DataLoader also caches within a single request: requesting the same user ID multiple times only fetches it once
3. Per-request DataLoader instances: DataLoader instances should be created per request (not shared across requests) to avoid leaking cached data between users. Create new DataLoader instances in the GraphQL context factory: context: ({ req }) => ({ db, user: req.user, userLoader: new DataLoader(batchUsers) })
4. When DataLoader is not enough: For very complex queries (N levels of nesting with M items at each level), DataLoader alone may not prevent performance issues. Consider: query complexity limits (reject queries above a certain complexity score), depth limits (reject queries nested more than N levels deep), and field-level rate limiting.
Practise GraphQL API design interview questions with HireStepX's AI voice interviewer. Get scored feedback on your schema design reasoning and explanation of the N+1 problem. First 2 sessions free.
Practice freeGraphQL subscriptions and Apollo Server
GraphQL subscriptions and tooling:
1. GraphQL Subscriptions: Subscriptions allow the server to push data to the client in real-time via WebSocket. The client subscribes to an event; the server publishes to that event when it occurs.
Example subscription: ```graphql subscription { messageSent(chatRoomId: '123') { id text sender { name } } } ``` The client receives a new event every time a message is sent to chat room 123.
Implementation: PubSub pattern; the mutation resolver publishes to a channel; the subscription resolver subscribes to that channel and pushes updates to the client via WebSocket. graphql-ws is the modern WebSocket library for GraphQL subscriptions (replaces the legacy subscriptions-transport-ws).
2. Apollo Server: Apollo Server is the most popular GraphQL server library for Node.js. Key features: schema stitching and federation (combine multiple GraphQL schemas into one), automatic persisted queries (APQ: reduces query size by sending a hash instead of the full query), tracing and logging, and plugin system.
3. Apollo Client (for frontend): Apollo Client is the most popular GraphQL client for React. Key features: normalised cache (entities are cached by ID, so updating a user in one query updates all queries that reference that user), optimistic UI updates, and reactive queries (UI automatically re-renders when the cache updates). React hooks: useQuery, useMutation, useSubscription.
Frequently asked questions
Explore more