GraphQL

intermediate 2-4 YOE GraphQL API query-language REST-vs-GraphQL

GraphQL was created by Facebook in 2012 to solve a very specific problem: their mobile app needed data from many different resources, and REST APIs were either sending too much data or requiring too many requests. Instead of the server deciding what data to return, GraphQL lets the client decide.

The Problem GraphQL Solves

Imagine we’re building a social media app. We want to show a user’s profile with their name, avatar, and last 3 posts (each with title and like count).

With REST (the problem)

Over-fetching: We call GET /users/123 and get back 30 fields — name, email, phone, address, bio, settings — when we only need name and avatar. We’re downloading data we don’t use.

Under-fetching: We need the user AND their posts. That’s two separate API calls: GET /users/123 then GET /users/123/posts. On a slow mobile connection, each round trip hurts.

The N+1 problem: We fetch 3 posts, and each post needs the author info. That’s 1 call for posts + 3 calls for authors = 4 requests total.

With GraphQL (the solution)

One request. We ask for exactly what we need:

query {
  user(id: 123) {
    name
    avatarUrl
    posts(last: 3) {
      title
      likeCount
    }
  }
}

Response — nothing more, nothing less:

{
  "data": {
    "user": {
      "name": "Manish",
      "avatarUrl": "https://...",
      "posts": [
        { "title": "System Design Basics", "likeCount": 42 },
        { "title": "CAP Theorem Explained", "likeCount": 28 },
        { "title": "REST vs GraphQL", "likeCount": 35 }
      ]
    }
  }
}

Core Concepts

Schema and Types

GraphQL has a strongly-typed schema. The schema defines what data exists and how it’s related:

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

type Post {
  id: ID!
  title: String!
  content: String!
  likeCount: Int!
  author: User!
}

type Query {
  user(id: ID!): User
  posts(limit: Int): [Post!]!
}

The ! means non-nullable. [Post!]! means a non-null list of non-null posts. The schema is the contract between frontend and backend.

Queries (Reading Data)

Queries are how we read data. The client specifies exactly which fields it wants:

# Just the name
query {
  user(id: 123) {
    name
  }
}

# Name + email + all posts with comments
query {
  user(id: 123) {
    name
    email
    posts {
      title
      comments {
        text
        author { name }
      }
    }
  }
}

Same endpoint, different shapes of data. The client controls it.

Mutations (Writing Data)

Mutations change data — create, update, delete:

mutation {
  createPost(input: {
    title: "New Post"
    content: "Hello world"
  }) {
    id
    title
    createdAt
  }
}

We can also ask for specific fields back in the response — so we don’t need a separate GET call after creating something.

Subscriptions (Real-Time Data)

Subscriptions let the client listen for real-time updates over WebSocket:

subscription {
  newMessage(chatId: "abc") {
    text
    sender { name }
    timestamp
  }
}

Whenever a new message is sent to that chat, the server pushes it to all subscribers.

REST vs GraphQL Comparison

AspectRESTGraphQL
EndpointsMultiple (/users, /posts)Single (/graphql)
Data fetchingServer decides what to returnClient decides what to return
Over-fetchingCommonImpossible (by design)
Under-fetchingCommon (need multiple calls)Solved (nested queries)
CachingEasy (HTTP caching by URL)Harder (single endpoint, POST requests)
File uploadsEasy (multipart)Awkward (needs workarounds)
Error handlingHTTP status codesAlways 200, errors in response body
Learning curveLowMedium
ToolingMature and everywhereGrowing fast

When to Use GraphQL

GraphQL shines when:

  • Our frontend needs data from multiple related resources in one call
  • Different clients (web, mobile, watch) need different data shapes
  • We’re tired of creating custom REST endpoints for each screen
  • Rapid frontend iteration — frontend can change data requirements without backend changes

Stick with REST when:

  • Our API is simple CRUD operations
  • We need HTTP caching heavily (GraphQL makes this harder)
  • We’re doing file uploads/downloads
  • Our team is small and the overhead of a GraphQL schema isn’t worth it
  • We’re building a public API (REST is more universally understood)

The Downsides

GraphQL isn’t perfect:

Complexity: We need a schema, resolvers, and a query engine. More moving parts than REST.

Caching is harder: REST uses URLs as cache keys. GraphQL uses a single endpoint with POST requests, so HTTP caching doesn’t work out of the box. We need client-side caching (Apollo Client, urql).

N+1 on the server: If a query asks for 100 posts with their authors, the resolver might make 100 separate database calls for authors. Solution: use DataLoader to batch and deduplicate.

Security: Clients can craft deeply nested queries that overload the server. We need query depth limiting and complexity analysis.

# A malicious query
query {
  user(id: 1) {
    posts {
      comments {
        author {
          posts {
            comments {
              author {
                # ...infinite nesting
              }
            }
          }
        }
      }
    }
  }
}

Key Takeaway

In simple language, GraphQL lets the client ask for exactly the data it needs in a single request — no over-fetching, no under-fetching. It’s great when we have complex, interconnected data and multiple client types. But it adds complexity compared to REST, especially around caching and security. For most simple APIs, REST is still the better choice. For data-rich applications with lots of relationships (social networks, dashboards, e-commerce), GraphQL really shines.