GraphQL is a query language for APIs and a server-side runtime that executes those queries against a strongly typed schema. Instead of calling many fixed endpoints, a client sends one request describing exactly the fields it needs, and the server returns a JSON response with the same shape.
Originally developed at Facebook for mobile applications and released as an open specification in 2015, GraphQL is now governed by the GraphQL Foundation. In the APIs landscape it sits alongside REST API, gRPC and JSON-RPC as a major style, and it is especially common where many different clients consume the same data.
How a GraphQL request is processed
Every GraphQL service starts with a schema written in the Schema Definition Language. The schema declares object types, their fields and the relationships between them, plus three root operation types: Query for reads, Mutation for writes and Subscription for real-time events.
When a request arrives, the server runs three phases. It parses the query text into a syntax tree, validates it against the schema so that unknown fields or wrong argument types are rejected before any work starts, and then executes it by calling a resolver function for each requested field. Resolvers can fetch data from databases, other services or caches.
The following table lists the core building blocks of a GraphQL API and their purpose.
| Element | Purpose | Example |
|---|---|---|
| Schema | Typed contract between client and server | type User { id: ID! name: String } |
| Query | Read operation | { user(id: 7) { name } } |
| Mutation | Write operation with a result | createOrder(input: ...) |
| Subscription | Stream of events over a long-lived connection | orderShipped { id } |
| Resolver | Function that produces a field’s value | Database lookup for User.name |
| Fragment | Reusable set of fields | ...UserCard in several queries |
Key features of the GraphQL type system
The type system is what distinguishes GraphQL from ad hoc JSON APIs. Fields are nullable by default and marked non-null with an exclamation mark, lists are explicit, and enums, interfaces, unions and input types model complex domains precisely.
Because the schema is machine-readable through introspection, tools can generate typed client code, autocomplete queries in editors and detect breaking changes automatically. Fields are retired with the @deprecated directive rather than by versioning the whole API, which encourages continuous schema evolution instead of v1 and v2 endpoints.
Errors follow a specific convention. A response normally returns HTTP status 200 even when some fields failed, with a data object holding whatever succeeded and an errors array describing the failures. This partial success model is useful but surprises teams used to status-code-based error handling.
GraphQL compared with other API styles
GraphQL solves over-fetching and under-fetching, the two classic problems of fixed endpoints: receiving more fields than needed, or needing several round trips to assemble one screen. It pays for this flexibility with a more complex server and harder caching.
| Aspect | GraphQL | REST API | gRPC |
|---|---|---|---|
| Endpoints | One endpoint, usually /graphql | Many resource URLs | Service methods |
| Response shape | Chosen by the client | Fixed by the server | Fixed by Protocol Buffers |
| Contract | Schema in SDL | Optional, often OpenAPI | Mandatory .proto files |
| Transport | Usually HTTP POST with JSON | HTTP verbs and status codes | HTTP/2 with binary payloads |
| HTTP caching | Limited, needs extra work | Native through GET and headers | Not applicable |
| Best fit | Varied clients and nested data | Simple, cacheable resources | Internal service-to-service calls |
The styles are not mutually exclusive. A common architecture places a GraphQL gateway in front of REST or gRPC microservices, giving front-end teams one flexible API while the backend keeps efficient internal protocols.
Performance and security considerations
Because clients control query shape, a GraphQL server must protect itself from expensive queries. A deeply nested query can multiply database work, and naive resolvers produce the N+1 problem, where fetching 100 orders triggers 100 separate customer lookups.
The table below maps the typical risks to their standard countermeasures.
| Risk | Symptom | Countermeasure |
|---|---|---|
| N+1 queries | Hundreds of small database calls per request | Batching with a DataLoader pattern |
| Deep or cyclic queries | Exponential work from nested relations | Depth limits, often 7 to 15 levels |
| Expensive field combinations | Slow responses and high CPU use | Query cost analysis and budgets |
| Schema exposure | Attackers map the entire API | Disable introspection in public production |
| Arbitrary queries | Unpredictable load and abuse | Persisted queries with an allowlist |
| Field-level data leaks | Users read fields they should not | Authorization in resolvers or a policy layer |
Caching also needs deliberate design. Since most requests are POST calls to one URL, standard HTTP caches do not help, so teams use normalized client caches keyed by type and ID, persisted queries sent as GET requests, or response caching at the gateway.
When to choose GraphQL
GraphQL is the stronger choice when several clients with different data needs share one backend and the domain is a graph of related entities. It is weaker for simple CRUD services, file transfer and public APIs that rely heavily on HTTP caching.
| Situation | Recommendation | Reason |
|---|---|---|
| Web and mobile apps sharing one backend | GraphQL | Each client fetches only what it shows |
| Aggregating many microservices | GraphQL gateway or federation | One schema hides service boundaries |
| Public, cache-heavy read API | REST API | CDN and browser caching work natively |
| High-throughput internal calls | gRPC | Binary encoding and streaming are faster |
| Event notifications to partners | Webhook | Push delivery without polling |
Stripped to the essentials
Imagine ordering in a restaurant where every dish comes as a fixed set menu. GraphQL is like ordering à la carte from one waiter: you list exactly what you want, and the kitchen brings precisely that, on one tray, in the order you asked.
For software, this means an app can ask a server for only the information it will display, in a single request. The menu itself, called the schema, is written down precisely, so both sides always know what can be ordered.
Where it is applied
A travel company runs a website, an iOS app and an Android app that all show bookings, flights, hotels and loyalty points. With its old REST API, the mobile booking screen needed six separate requests and downloaded many fields it never displayed, which made the screen slow on weak mobile connections.
The company adds a GraphQL layer in front of its existing services. The mobile screen now sends one query naming the 15 fields it needs, while the website requests a richer set from the same schema. Resolvers use batching, so a page with 50 bookings makes one hotel lookup instead of 50. The mobile payload shrinks considerably and front-end teams can add fields to screens without waiting for new backend endpoints.