GraphQL is a query language for APIs and a runtime for fulfilling those queries. Instead of many fixed REST endpoints each returning a fixed shape, GraphQL exposes a single endpoint backed by a strongly-typed schema, and lets the client ask for exactly the data it needs — no more, no less — in one request. It was created at Facebook to solve the over-fetching and round-trip problems of mobile clients consuming REST APIs. This page explains the core ideas: the single endpoint, the type system, the three operation types, how a query maps to your data, and the trade-offs versus REST.
The core idea — ask for exactly what you need
A query (what the client sends)
GRAPHQL
query {
user(id: "42") {
name
email
orders(last: 3) {
id
total
items { name price }
}
}
}
The response — the same shape as the query, nothing extra
The response mirrors the query — the client declares the shape, and gets exactly that, in one request
GraphQL's defining trait: the **response shape matches the request shape**. The client specifies which fields it wants — including nested, related data across what would be several REST resources — and the server returns precisely those fields in the same structure. A mobile screen fetches a user, their last three orders, and each order's items in *one* round-trip, requesting only the handful of fields it renders. This directly attacks the two classic REST pains: **over-fetching** (an endpoint returns 30 fields when you need 3) and **under-fetching / the N+1 round-trips** (you call `/users/42`, then `/users/42/orders`, then `/orders/o1/items` in waterfalls).
One endpoint, one schema
Text
REST: many endpoints, fixed shapes GraphQL: one endpoint, flexible queries
GET /users/42 POST /graphql
GET /users/42/orders { query: "{ user(id:42){...} }" }
GET /orders/o1/items → server resolves the whole tree
POST /users
...dozens of routes...
GraphQL has a single endpoint (`POST /graphql`); the query in the body decides what runs
Rather than dozens of URL routes, a GraphQL API exposes **one endpoint** (conventionally `POST /graphql`). What distinguishes one operation from another isn't the URL or HTTP method — it's the **query in the request body**. The server parses that query, validates it against the **schema** (a typed contract describing every available field and type), and executes it. This means versioning, discovery, and documentation all flow from the schema: clients can introspect it to learn the entire API, and tooling generates types and autocompletion from it. The schema is the single source of truth for what the API can do.
The type system
A schema defines types and the fields each exposes
GRAPHQL
type User {
id: ID! # ! means non-nullable
name: String!
email: String!
orders: [Order!]! # a non-null list of non-null Orders
}
type Order {
id: ID!
total: Float!
items: [Item!]!
}
type Query { # the entry points for reading data
user(id: ID!): User
orders: [Order!]!
}
The schema is a strongly-typed contract — every field has a type, and the server validates queries against it
GraphQL is **strongly typed**. The schema declares object types (`User`, `Order`), the fields each has, and the type of every field — including scalars (`ID`, `String`, `Int`, `Float`, `Boolean`), lists (`[Order!]`), and whether each is nullable (`!` marks non-null). Before executing, the server **validates** the incoming query against this schema: ask for a field that doesn't exist or pass a wrong argument type and the query is rejected with a clear error *before any resolver runs*. This contract powers code generation, editor autocompletion, and confidence that client and server agree on shapes — [Schemas & Type Definitions](/nodejs/graphql-schema) covers it in depth.
The three operation types
Operation
Purpose
REST analogy
Query
Read data (no side effects)
GET
Mutation
Write data — create/update/delete
POST/PUT/PATCH/DELETE
Subscription
Real-time stream of updates over a persistent connection
Queries read, mutations write, subscriptions stream — the three things a GraphQL API can do
GraphQL operations come in three kinds. **Queries** read data and are expected to be side-effect free (like `GET`). **Mutations** change data — create, update, delete — and run sequentially so one completes before the next begins. **Subscriptions** open a long-lived connection (typically over [WebSockets](/nodejs/websockets)) and push updates to the client as events occur, GraphQL's answer to real-time. All three use the same schema and the same field-selection syntax; the only difference is intent and execution semantics. Most APIs are dominated by queries and mutations, with subscriptions added where live updates are needed.
How a query becomes data — resolvers
JS
// Each field can have a RESOLVER — a function that returns its value.
const resolvers = {
Query: {
user: (parent, args, context) => context.db.users.findById(args.id),
},
User: {
// Resolves User.orders lazily — only runs if the query asked for orders:
orders: (user, args, context) => context.db.orders.findByUser(user.id),
},
}
A resolver is a function that fetches one field's value — the server walks the query tree calling them
The schema describes *what* data exists; **resolvers** describe *how* to fetch it. Each field can have a resolver function, and the server executes a query by walking its tree top-down, calling the resolver for each requested field and passing the parent's result down to its children. Resolvers can pull from a database, call another service, or compute a value — GraphQL doesn't care where data comes from, which is why it can sit in front of many backends at once. Crucially, a field's resolver only runs if the client *asked* for that field, which is what makes selective fetching efficient. [Resolvers](/nodejs/graphql-resolvers) covers the execution model and the infamous N+1 pitfall.
The trade-offs
GraphQL's flexibility shifts complexity to the server — caching, query-cost control, and N+1 are real challenges
GraphQL isn't free wins. Because clients compose arbitrary queries, several things get *harder* than in REST: **HTTP caching** doesn't work out of the box (everything is a `POST` to one URL, so you cache at the field/resolver layer instead); **performance is unpredictable** since a single query can request deeply nested data, demanding query-depth/complexity limits to prevent abuse; the **N+1 query problem** appears naturally and needs batching (DataLoader); and there's real **setup overhead** (schema, resolvers, server). The benefits — exact-shape fetching, one round-trip, a typed self-documenting contract, evolving without versioning — are substantial for complex clients, but for a simple CRUD API [REST](/nodejs/graphql-vs-rest) is often the lighter, better choice. Pick deliberately.
Next
Build a GraphQL server in Node: [Apollo Server](/nodejs/apollo-server).