What is GraphQL?
GraphQL is a query language for APIs and a runtime for executing those queries. Originally developed internally at Facebook in 2012 to power their mobile apps, it was open-sourced in 2015 and has since been adopted by companies including GitHub, Twitter, Shopify, Airbnb, and The New York Times.
Unlike traditional REST APIs — which expose a fixed set of endpoints each returning a predetermined data structure — GraphQL exposes a single endpoint where clients describe precisely what data they need. The server returns exactly that, no more and no less.
Think of REST as a restaurant with a fixed menu: you order a meal and get what comes with it. GraphQL is more like ordering off the menu item by item: you specify each ingredient you want, and the kitchen assembles exactly that plate.
GraphQL is not a library, framework, or database — it is a specification. Implementations exist in virtually every language, including JavaScript (Apollo Server, graphql-js), Python (Strawberry, Graphene), Ruby (graphql-ruby), Go (gqlgen), Java (graphql-java), and many more.
How GraphQL Works
A GraphQL API operates through three core concepts: a schema, resolvers, and operations.
The schema is a type system written in the GraphQL Schema Definition Language (SDL). It describes every data type your API exposes and the operations clients can perform. The schema acts as a contract between the client and server.
Resolvers are functions that fulfil each field in the schema. When a client requests a field, the resolver for that field runs and fetches the data — from a database, a REST API, a cache, or anywhere else.
Operations are what clients send to the API. There are three types: queries (read), mutations (write), and subscriptions (real-time updates). All operations are sent as POST requests (or GET for queries) to a single endpoint, typically /graphql.
Here is a simple example. Suppose you have a blogging platform. With REST, to show a post and its author's name, you might need two requests: GET /posts/42 and GET /users/7. With GraphQL, you send one query:
query {
post(id: "42") {
title
body
author {
name
}
}
}
The server returns exactly that structure — no extra fields, no extra round trips.
REST vs GraphQL: Key Differences
| Feature | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple (one per resource) | Single endpoint |
| Data fetching | Fixed response structure | Client specifies exact fields |
| Over-fetching | Common — extra fields returned | Never — only requested fields returned |
| Under-fetching | Common — requires multiple requests | Eliminated — one query fetches all |
| Versioning | URL versioning (/v1/, /v2/) | Schema evolution, deprecation |
| HTTP Caching | Built-in (GET requests cacheable) | Requires custom caching (e.g. persisted queries) |
| Type system | Optional (OpenAPI/Swagger) | Built-in and required |
| Learning curve | Low | Moderate |
| Tooling | Mature, widespread | Rich but more specialized |
| Best for | Simple, public APIs | Complex, multi-client apps |
Queries, Mutations, and Subscriptions
GraphQL has three operation types, each serving a distinct purpose.
Queries
Queries fetch data. They are analogous to GET requests in REST. Queries are declarative — you write what you want in a structure that mirrors the response shape:
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
title
createdAt
}
}
}
Mutations
Mutations modify data — creating, updating, or deleting records. They are analogous to POST, PUT, PATCH, and DELETE in REST:
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
createdAt
}
}
Subscriptions
Subscriptions enable real-time functionality. They maintain a persistent connection (typically via WebSocket) and push updates to the client when data changes. This is useful for live feeds, chat applications, and dashboards:
subscription OnCommentAdded($postId: ID!) {
commentAdded(postId: $postId) {
content
author { name }
}
}
Understanding the GraphQL Schema
The schema is the backbone of every GraphQL API. Written in SDL (Schema Definition Language), it defines types and the relationships between them. Here is a minimal example:
type Post {
id: ID!
title: String!
body: String!
author: User!
comments: [Comment!]!
}
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Query {
post(id: ID!): Post
posts: [Post!]!
user(id: ID!): User
}
type Mutation {
createPost(title: String!, body: String!): Post!
}
The exclamation mark (!) means a field is non-nullable. The Query type defines all read operations. The Mutation type defines all write operations. Every field in the schema must have a corresponding resolver function on the server.
__schema and __type. This powers tools like GraphiQL and Apollo Studio, which provide auto-complete and documentation from your live schema automatically.
Advantages of GraphQL
No Over-fetching or Under-fetching
With REST, an endpoint for a user profile might return 40 fields when your mobile app only needs 3. GraphQL eliminates this by letting the client specify exactly what it needs. This is especially valuable on mobile networks where bandwidth matters.
Single Request for Complex Data
Fetching a blog post with its author, comments, and each comment's author in REST might require 4+ API calls. In GraphQL, one query retrieves all of it. Fewer round trips means faster pages.
Strongly Typed Schema
The schema enforces types at the API boundary. Clients always know what fields are available and what types they return. This makes code generation, IDE auto-complete, and automated testing straightforward.
Evolve Without Breaking Changes
REST APIs often require versioning when the shape of a response changes. GraphQL handles evolution through deprecation: you can mark fields as deprecated and add new fields without creating a new API version, giving clients time to migrate.
Excellent Developer Experience
Tools like GraphiQL, Apollo Studio, and Insomnia provide interactive playgrounds where you can explore your schema, write queries with auto-complete, and inspect results — all in the browser.
Disadvantages and Trade-offs
HTTP Caching Is Harder
REST GET requests are naturally cacheable by browsers and CDNs. GraphQL sends queries as POST requests by default, which are not cached. Solutions exist (persisted queries, GET requests for read-only queries, Apollo Client's in-memory cache), but they add complexity.
N+1 Query Problem
If your resolvers are naive, fetching a list of 100 posts and their authors might trigger 100 separate database queries — one per author. This "N+1 problem" requires deliberate solutions, most commonly the DataLoader batching pattern.
File Uploads Are Awkward
GraphQL was not designed with file uploads in mind. The multipart request specification for GraphQL exists but is non-standard, and many implementations handle it differently.
Overkill for Simple APIs
If your API has three or four straightforward endpoints, GraphQL adds significant overhead: schema definition, resolver wiring, and tooling setup. REST is much simpler for simple use cases.
When to Use GraphQL vs REST
Choose GraphQL when: you have multiple clients (web, mobile, desktop) with different data needs; your data model is complex with many relationships; you want to avoid versioning headaches; or you're building a product where developer experience and rapid iteration matter.
Choose REST when: you're building a simple or public API; you need straightforward HTTP caching; your team is unfamiliar with GraphQL; you're building file-heavy APIs; or you need maximum compatibility with third-party tooling.
Use both when: many mature companies run both. REST for public-facing, cacheable, simple endpoints. GraphQL for internal, client-driven, complex data operations. There is no rule that says you must pick one.