A Gentle Introduction to GraphQL

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.