Skip to content
C Codeloom
GraphQL

What Is GraphQL? A Query-First API Style

A clear introduction to GraphQL — a single endpoint, exact-fields queries, a typed schema, and how it compares to REST. Learn when GraphQL fits and when it doesn't.

·9 min read · By Yash Kesharwani
Beginner 11 min read

What you'll learn

  • What GraphQL is — and what problem it was designed to solve
  • How a single endpoint and a typed schema change API design
  • The three operation types: queries, mutations, subscriptions
  • How GraphQL compares to REST in practice
  • When GraphQL is worth the extra machinery — and when it isn't

Prerequisites

  • A rough idea of HTTP requests and responses
  • Comfort with JSON
  • Optional but useful: a quick read of What Is REST?

GraphQL is a query language for your API and a runtime that executes those queries against your data. It was invented at Facebook in 2012 and open-sourced in 2015. The headline idea: instead of the server defining many endpoints with fixed response shapes, the client asks for exactly the fields it needs from a single endpoint, and the server returns exactly that.

This post explains what that means in practice and when you should reach for it.

The shape of a GraphQL request

In REST, you call several URLs and stitch results together. In GraphQL, you POST a query to one URL:

POST /graphql
Content-Type: application/json

# The body is a JSON object with a "query" string
{
  "query": "{ user(id: 42) { name email } }"
}

The server replies with a JSON object whose data field mirrors the shape of the query:

{
  "data": {
    "user": {
      "name": "Alice",
      "email": "alice@example.com"
    }
  }
}

No id field appeared in the request, so no id appeared in the response. That is the central promise: you get what you ask for, nothing more.

A more interesting query

GraphQL really shines when data is nested. A single request can walk through relationships:

# Fetch a user, their last 3 orders, and the items in each order
{
  user(id: 42) {
    name
    orders(last: 3) {
      id
      total
      items {
        name
        price
      }
    }
  }
}

In a REST API, the same data would typically take three round trips: one for the user, one for their orders, one for items in each order. With GraphQL, the client describes the whole tree once and the server resolves it.

GraphQL vs REST in one table

ConcernRESTGraphQL
EndpointsMany (/users, /users/42/orders)One (/graphql)
HTTP methodsGET, POST, PUT, PATCH, DELETEAlmost always POST
Response shapeFixed by serverShaped by the query
VersioningURL or header (/v2/...)Evolve the schema; deprecate fields
CachingHTTP caching is built inNeeds client-side or per-resolver caching
Toolingcurl, Postman, browser DevToolsGraphiQL, Apollo, codegen

Neither is “better.” They make different trade-offs.

Schema first

A GraphQL API is defined by a schema written in the Schema Definition Language (SDL). The schema is the source of truth for what the server can do.

# A typed description of the API surface
type User {
  id: ID!
  name: String!
  email: String!
  orders: [Order!]!
}

type Order {
  id: ID!
  total: Float!
  items: [Item!]!
}

type Item {
  name: String!
  price: Float!
}

type Query {
  user(id: ID!): User
  users: [User!]!
}

A few syntax notes:

  • String, Int, Float, Boolean, ID are the built-in scalar types.
  • ! means non-null — the field is guaranteed to be present.
  • [User!]! is a non-null list of non-null Users.
  • Query is the root type for read operations; every entry point lives here.

The schema is the contract. Clients can introspect it at runtime, which is what powers tools like GraphiQL — autocomplete, inline docs, and type-checked queries in the browser. We go deeper in GraphQL Schemas and Resolvers.

Operations: query, mutation, subscription

GraphQL has three operation types.

Query — read data

# A read operation
query GetUser {
  user(id: 42) {
    name
  }
}

Mutation — change data

# A write operation; conventionally returns the modified resource
mutation CreatePost {
  createPost(input: { title: "Hello", body: "World" }) {
    id
    title
  }
}

By convention, mutations execute serially while queries execute in parallel. A mutation typically returns the new or updated object so the client can update its cache without a second round trip.

Subscription — real-time updates

# A long-lived stream — usually over WebSockets
subscription OnNewComment {
  commentAdded(postId: "42") {
    id
    body
    author { name }
  }
}

Subscriptions push events from server to client. They are powerful but operationally heavier — most teams add them only when they have a clear use case (live chat, notifications, dashboards).

Variables

Hard-coding values into a query string is the GraphQL equivalent of SQL injection. Use variables instead:

# Declared once at the top of the operation
query GetUser($id: ID!) {
  user(id: $id) {
    name
    email
  }
}
// Variables are sent alongside the query
{
  "query": "query GetUser($id: ID!) { user(id: $id) { name email } }",
  "variables": { "id": "42" }
}

Variables are typed ($id: ID!), validated by the schema, and reusable.

Try it yourself. Open https://graphql.org/swapi-graphql/ (a public Star Wars GraphQL playground) and run this query:

{
  allFilms(first: 3) {
    films { title director releaseDate }
  }
}

Then add producers to the selection. Notice you didn’t have to change anything on the server — you just asked for a new field. That is the developer-experience promise of GraphQL.

Fragments

When the same fields are needed in multiple places, extract a fragment:

# Reusable selection set
fragment UserCard on User {
  id
  name
  email
}

query {
  me { ...UserCard }
  user(id: 42) { ...UserCard }
}

Fragments are how large GraphQL codebases stay maintainable. UI components own their own fragments — each declares the data it needs and composes upward.

Errors

GraphQL responses always return HTTP 200, even when something went wrong. Errors land in a top-level errors array:

{
  "data": { "user": null },
  "errors": [
    {
      "message": "User 999 not found",
      "path": ["user"],
      "extensions": { "code": "NOT_FOUND" }
    }
  ]
}

This catches REST developers by surprise. The HTTP layer says “request handled”; the body says what actually happened. Clients must check errors after every call.

Where GraphQL helps

GraphQL pays for its complexity when:

  • The client’s data needs are varied and nested. A frontend that mixes users, orders, products, reviews, and recommendations on one screen benefits enormously from a single typed query.
  • You have many client teams hitting one backend. Each team picks the fields it needs without backend changes.
  • Over-fetching is a real cost. Mobile apps on slow networks particularly benefit from getting only what they will render.
  • You want a strongly typed contract. Codegen tools turn the schema into typed client code, so your editor catches mismatches before runtime.

Where GraphQL hurts

It is not a free upgrade. Consider sticking with REST when:

  • Your API is mostly simple CRUD. A GraphQL server adds layers (schema, resolvers, dataloader) that a few REST routes don’t need.
  • You rely on HTTP caching, CDNs, or HTTP-aware tooling. Since everything is a POST to /graphql, intermediate caches can’t help you for free.
  • Your consumers are partners or webhooks. Plain HTTP + JSON is more familiar and easier to call from any environment.
  • You don’t have anyone who has run a GraphQL server in production. The operational learning curve (query depth limits, persisted queries, N+1 problems) is real.

Many companies ship both: REST for partner integrations and webhooks, GraphQL for their own frontend.

The N+1 problem (a teaser)

The most famous GraphQL footgun:

# Looks innocent — but every order may trigger its own SQL query
{
  users {
    name
    orders { total }
  }
}

If the server fetches orders per user naively, 100 users means 1 query for users plus 100 queries for orders. This is the N+1 problem, and the standard fix is a tool called DataLoader that batches requests within a single query execution. We unpack it in GraphQL Schemas and Resolvers.

Servers and clients you will hear about

On the server side, the popular choices today:

  • Apollo Server (Node) — the default in many JavaScript shops
  • GraphQL Yoga (Node) — lightweight, batteries-included
  • graphql-ruby, graphene (Python), gqlgen (Go), Hot Chocolate (.NET)

On the client side:

  • Apollo Client and urql for React
  • Relay — Facebook’s own client, deeply integrated with fragments
  • graphql-request — a minimal fetch wrapper for simple cases

You don’t need a heavy client. fetch('/graphql', { method: 'POST', body: JSON.stringify({ query }) }) works.

Try it yourself. Take a small REST API you’ve used (or the one from Build Your First REST API with Express) and sketch its GraphQL schema. Define type Todo, a Query with todos and todo(id: ID!), and a Mutation with createTodo, updateTodo, deleteTodo. You won’t write a server here — just the SDL. Notice how the surface shrinks from many endpoints to a handful of fields.

A pragmatic mental model

Stop thinking of GraphQL as a replacement for REST. Think of it as a query layer over your existing services. The resolvers behind the schema can call:

  • Your existing REST endpoints
  • A SQL database directly
  • Microservices over gRPC
  • Third-party APIs

The schema is the public face. The implementation under each field can be whatever you already have.

Recap

You now know:

  • GraphQL is a schema-defined, query-first API style with a single endpoint
  • Clients ask for exactly the fields they need; servers return that shape
  • The three operations are query (read), mutation (write), subscription (real-time)
  • The schema (SDL) is the contract; resolvers implement each field
  • GraphQL excels with varied nested data; REST excels with simple CRUD and caching
  • The classic pitfall is the N+1 problem, solved with batching tools like DataLoader

Next steps

The natural follow-up is to learn how schemas and resolvers fit together — how a typed declaration becomes running code.

Next: GraphQL Schemas and Resolvers

If you want to compare from the other side, revisit What Is REST? with GraphQL fresh in mind. The contrast makes both styles easier to reason about.

Questions or feedback? Email codeloomdevv@gmail.com.