REST APIs typically force a choice: return too little data (requiring multiple round-trips) or too much (wasting bandwidth on fields the client never uses). GraphQL’s core idea is letting the client specify exactly what shape of data it needs, in a single request.
A Query Requests Exactly What It Needs
query {
user(id: "42") {
name
email
posts(limit: 5) {
title
publishedAt
}
}
}
One request, one response, shaped exactly like the query — no over-fetching unused fields, no separate round-trip to a /users/42/posts endpoint.
A Minimal Server with Apollo
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql<code>
type Post {
title: String
publishedAt: String
}
type User {
id: ID!
name: String
email: String
posts(limit: Int): [Post]
}
type Query {
user(id: ID!): User
}
</code>;
const resolvers = {
Query: {
user: (_, { id }) => getUserFromDatabase(id),
},
User: {
posts: (user, { limit }) => getPostsForUser(user.id, limit),
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => console.log(<code>Ready at ${url}</code>));
The Trade-offs Nobody Mentions in the Pitch
- Caching is harder — REST’s URL-based caching (CDNs, browser cache) doesn’t map cleanly onto a single POST endpoint serving arbitrary queries.
- The N+1 query problem — naive resolvers can trigger a database query per item in a list; tools like DataLoader exist specifically to batch these.
- Overkill for simple CRUD APIs — a small service with five straightforward endpoints rarely benefits enough to justify the added complexity.
When It’s Actually Worth It
GraphQL earns its complexity when you have genuinely varied clients (a mobile app, a web dashboard, a partner integration) each needing different shapes of the same underlying data, or deeply nested/relational data where REST’s endpoint-per-resource model gets unwieldy fast.