Ask ten developers what GraphQL is and you will get answers ranging from "a database query language" to "a replacement for REST" to "a Facebook thing." None of those is quite right, and the confusion is understandable because GraphQL sits in a part of the stack that most people learn by osmosis rather than by design. This guide fixes that. GraphQL is an API query language and a runtime for fulfilling those queries against your data, and once you see the specific problem it was invented to solve, every design choice in it starts to make sense. If you want the wider map of how APIs, message queues, files and events fit together across an enterprise, start with the enterprise system integration pillar, then come back here for the API-layer detail.
The message up front: GraphQL is not a better REST or a worse REST. It is a different contract. REST exposes a set of resources at many URLs and the server decides the response shape. GraphQL exposes one endpoint and a typed schema, and the client decides the response shape by writing a query. Understanding that single inversion, who controls the shape of the response, explains almost everything else about the technology.
1. What GraphQL is
GraphQL is a specification, published and maintained as an open standard, that defines two things: a query language for reading and writing data, and a type system that describes what data is available and how it is connected. It was developed at Facebook in 2012 to power its mobile applications, released publicly in 2015, and is now governed by the GraphQL Foundation. It is language-agnostic. There are GraphQL server implementations in JavaScript, Java, C#, Go, Python, PHP and most other mainstream languages, and clients that speak it on the web, mobile and server side.
The word "graph" in the name is the important clue. GraphQL models your data as a graph of connected objects rather than as a list of endpoints. A customer has orders, an order has line items, a line item has a product, a product has a supplier. In a GraphQL schema those relationships are declared once, and a client can traverse them in a single request, walking from a customer straight through to the suppliers of the products in their most recent order, without the server having to expose a bespoke URL for that exact journey.
It is worth being precise about what GraphQL is not. It is not a database and it does not talk to your database directly. It sits in front of whatever data sources you already have: relational databases, existing REST services, microservices, third-party APIs, in-memory caches, files. It is a layer that presents a unified, typed, queryable interface over those sources. That is why teams often introduce GraphQL as a gateway or "backend for frontend" in front of systems that already exist, rather than as a rewrite of them.
2. The problem it solves: over-fetching and under-fetching
To understand why GraphQL exists you have to feel the pain it was built to remove, and that pain has two names: over-fetching and under-fetching. Both come from the same root cause in a conventional REST design, which is that the server decides the shape of every response and the client has to live with it.
Over-fetching is when an endpoint hands you far more data than you need. A mobile screen wants to show a user's name and avatar, so it calls /users/42, and the server returns the full user object: name, avatar, email, phone, address, preferences, timestamps, internal flags, forty fields where the screen needed two. On a fast desktop connection nobody notices. On a phone on a weak mobile network, that waste is real: more bytes over the wire, more parsing, more battery, a slower screen. Multiply it across every screen in an app and it becomes the reason the app feels sluggish.
Under-fetching is the opposite and often worse. A single screen needs data that lives behind several endpoints, so one logical view becomes a waterfall of calls. Load the user from /users/42, then their orders from /users/42/orders, then for each order fetch the product details from /products/{id}. This is the classic "N+1 requests" problem at the network layer: one request to get the list, then N more to fill in the details. Each round trip adds latency, and on a high-latency mobile link a screen that needs five sequential calls can take a visibly long time to assemble.
Facebook hit both problems hard because it was building rich mobile experiences over slow and unreliable networks, where every wasted byte and every extra round trip was expensive. GraphQL's answer is to let the client describe, in one request, the exact tree of data the screen needs, no more and no less, and to let the server resolve that whole tree in a single round trip. Over-fetching disappears because you only ask for the fields you want. Under-fetching disappears because related data is fetched together by traversing the graph. That is the entire origin story, and everything else in the design serves it.
3. Schema, types and resolvers
The heart of a GraphQL service is its schema. The schema is a strongly typed contract, written in GraphQL's Schema Definition Language, that declares every type of object the API knows about, the fields on each type, and how those types relate. Nothing can be queried that is not in the schema, and the schema is introspectable, meaning a client (or a tool) can ask the server to describe itself. That introspection is what powers the excellent tooling around GraphQL: auto-completing query editors, generated documentation and type-safe client code.
A small slice of a schema reads almost like plain description. A Customer type has an id, a name, an email, and a list of orders. An Order type has an id, a total, a status and a list of lineItems. Because orders on the customer points at the Order type, and lineItems points onward, the graph is fully described and the client can traverse it. Scalar fields (strings, integers, booleans, IDs) are the leaves; object fields are the branches. GraphQL also enforces nullability explicitly, so the schema states which fields are guaranteed to return a value and which may be null, and that information flows all the way into typed client code.
The schema says what data exists. Resolvers are the code that says where it comes from. For every field a client can request, the server has a resolver function whose job is to produce the value for that field. The resolver for Customer.orders might run a SQL query, call an existing orders microservice, or read a cache. The resolver for a product's supplier might hit a completely different system. This is the mechanism that lets a single GraphQL query stitch together data from many backends: each field is resolved independently, by whatever code knows how to fetch it, and GraphQL assembles the results into the shape the query requested.
This separation is the source of GraphQL's flexibility and, as we will see later, one of its performance traps. The schema is the stable public contract that clients depend on. The resolvers behind it can change freely: you can swap a database for a microservice, split one backend into three, or add a cache, and as long as the schema stays the same, clients never notice.
4. Queries, mutations and subscriptions
GraphQL defines three operation types, and together they cover the full read-write-realtime surface of an API.
- Queries read data. A query is a request for a specific tree of fields, and the response comes back in exactly the same shape as the query, which is one of GraphQL's most pleasant properties: the response mirrors the request, so there are no surprises about structure. Queries are, by convention, side-effect free, the equivalent of a safe GET in REST.
- Mutations write data. Creating a customer, updating an order status, deleting a record: all of these are mutations. A mutation runs its change and can return data in the same query-shaped way, so after updating an order you can ask, in the same request, for the new status and any recalculated totals. Mutations execute in series, one after another, which gives predictable ordering when a request contains several.
- Subscriptions stream data. A subscription is a long-lived connection, usually over WebSockets, where the server pushes updates to the client whenever a relevant event occurs: a new message in a chat, a live price change, a shipment status update. This is GraphQL's answer to real-time data, and it uses the same schema and type system as queries, so the shape of a pushed event is described exactly like everything else.
A tiny query makes the request-shaped-response idea concrete. The following asks for one customer, just their name, and the total and status of each of their orders, and nothing else:
customer(id: 42) {
name
orders {
total
status
}
}
}
The response is a JSON object with exactly that structure: a customer containing a name and an orders array where each order carries only a total and a status. No email, no addresses, no line items, because none were asked for. If the same screen later needs the line items, the client adds those fields to the query and the server returns them, with no new endpoint and no server change required. That is the whole promise of GraphQL in one small example.
5. A single endpoint
The structural difference that people notice first is that a GraphQL API usually has exactly one endpoint, conventionally /graphql, and every operation is a POST to that endpoint carrying the query in its body. This is the opposite of REST, where the URL itself is the address of the resource and you have many of them. In GraphQL the URL is fixed and the query describes what you want. The diagram below contrasts the two: on the left a client makes several REST calls to several URLs and assembles the result itself; on the right the same client sends one query to one endpoint and receives exactly the shape it asked for.
The single endpoint has practical consequences beyond tidiness. Because the server resolves the whole query in one pass, it can fetch the related pieces in parallel and batch calls to the underlying data sources, collapsing what would have been an N+1 network waterfall into a single coordinated resolution. The client code also simplifies: instead of orchestrating several calls and merging the responses by hand, it sends one request and gets back a ready-shaped object. This is precisely why GraphQL feels so good on the frontend, and why it is often introduced as an aggregation layer in front of many existing services rather than as a replacement for any one of them.
6. GraphQL versus REST
The most common question I get is simply "should we use GraphQL or REST." That framing is slightly wrong, because the two are not strictly competitors, many systems run both, but a direct comparison across the dimensions that matter clarifies the trade. The table below lays out where each approach sits.
| Dimension | REST | GraphQL |
|---|---|---|
| Endpoints | Many URLs, one per resource | One endpoint for everything |
| Data fetching | Server decides the response shape | Client decides the response shape |
| Over & under fetching | Common; fixed fields, multiple calls | Avoided by design; exact fields, one call |
| Versioning | Often /v1, /v2 style versioned URLs | Evolve the schema; add fields, deprecate old ones |
| Caching | Simple; HTTP caching on URLs works out of the box | Harder; one POST URL, needs client or field-level caching |
| Learning curve | Low; familiar HTTP verbs and status codes | Higher; schema, resolvers, query language, tooling |
Read that table honestly and neither column is a clean winner. GraphQL wins decisively on data-fetching efficiency and on evolving the contract without breaking clients. REST wins on simplicity and on caching, and its familiarity should not be underrated on a team that already knows it well. If you want the deeper REST treatment, see the REST API explained pillar, and for how REST relates to the older enterprise style, the REST versus SOAP comparison. GraphQL is also not the only modern alternative worth knowing: for high-throughput service-to-service traffic, gRPC occupies a different and complementary niche.
7. Strengths and honest limits
GraphQL's strengths are real and I do not want to undersell them. A single request that returns exactly the needed data removes a whole class of frontend performance problems. The strongly typed, introspectable schema is genuinely excellent developer experience: auto-completion, generated types, live documentation and validation before a query ever runs. Evolving the API by adding fields and deprecating old ones, rather than minting new versioned URLs, keeps clients working across change. For a single graph aggregating many backends behind one contract, it is a powerful pattern.
But there are three limitations that every team adopting GraphQL runs into, and pretending otherwise helps nobody.
Caching is harder. REST rides on decades of HTTP caching infrastructure: a GET to a stable URL can be cached by browsers, CDNs and proxies with almost no effort. GraphQL sends everything as a POST to one URL, so that free layer of caching does not apply. You get caching back, but you have to build it deliberately, either with a sophisticated client cache that understands the graph, or with server-side field-level and persisted-query caching. It is solvable, but it is work that REST gives you for nothing.
Complexity is higher. A GraphQL server is more machinery than a REST controller. You define a schema, write resolvers, wire up the type system, and adopt a query language and tooling the whole team has to learn. For a small API with a handful of endpoints, that overhead can outweigh the benefit. GraphQL earns its complexity when you have many clients with divergent data needs, or many backends to aggregate, not when you have one simple service.
The honest limitation: the N+1 problem moves, it does not vanish. GraphQL removes the N+1 problem at the network layer, one query instead of many, but it can quietly recreate it at the resolver layer. If a query asks for a list of customers and each customer's orders, a naive implementation runs one database query for the customers and then one more query per customer for their orders: N+1 again, now inside the server. The fix is a batching-and-caching pattern (the well-known DataLoader approach) that collects the per-item lookups within a single resolution and issues them as one batched query. It works well, but you have to know to build it. A GraphQL server written without batching can hammer a database far harder than the equivalent REST endpoint.
There is also a security and cost dimension worth naming: because clients compose their own queries, a malicious or careless client can craft a deeply nested query that is expensive to resolve. Mature GraphQL deployments add query depth limits, complexity scoring, timeouts and persisted (allow-listed) queries to keep that under control. None of this is a reason to avoid GraphQL. It is a reason to adopt it with eyes open, budgeting for the caching, batching and safeguarding work that a robust deployment needs.
8. When to use GraphQL
After all the theory, the practical question is when GraphQL is the right choice, and the answer is pleasingly concrete. It shines in a recognisable set of situations, and it is overkill in an equally recognisable set.
Reach for GraphQL when:
- You have multiple client types (web, iOS, Android, partner integrations) with genuinely different data needs, and you are tired of building bespoke endpoints for each or forcing them all through one over-fetching endpoint.
- You are aggregating several backend services or databases behind one API, and you want clients to see a single coherent graph rather than a patchwork of services.
- Your frontends iterate fast and constantly change what data they show, and you want them to adjust queries without waiting on backend endpoint changes.
- Network efficiency matters: mobile clients on slow links where over-fetching and multiple round trips are a measurable cost.
Stick with REST when:
- You have a small, stable API with a handful of resources and one main client. The GraphQL machinery is overhead you do not need.
- HTTP caching and CDN behaviour are central to your performance strategy, and you do not want to rebuild that layer.
- Your team knows REST well and the data-fetching pain GraphQL solves is not pain you actually feel.
In enterprise integration work I rarely see it as an exclusive choice. A common and sensible pattern is a GraphQL gateway that presents a unified graph to frontend teams while calling REST services, message queues and databases underneath, each staying REST or event-driven where that suits it best. GraphQL becomes the client-facing aggregation layer, not a mandate that rewrites everything below it. If your integration surface includes an ERP such as Microsoft Dynamics 365 Business Central, its own API story is worth understanding alongside this: see Business Central APIs and integrations for how a major platform exposes REST and OData endpoints that a GraphQL layer might sit in front of.
The integration lens: GraphQL is one API style among several, and choosing it is a design decision inside a larger integration architecture, not a default. The enterprise system integration pillar frames that decision properly: match the integration style to the interaction. Synchronous request-response with flexible client-driven shapes leans toward GraphQL; simple resource CRUD leans toward REST; high-throughput internal calls lean toward gRPC; and asynchronous, decoupled flows lean toward messaging and events. GraphQL is a strong tool for the first case and the wrong tool for the last.
9. References
The claims in this article rest on the primary sources for the technology, and I would encourage anyone going deeper to read them directly rather than relying on secondary summaries:
- The GraphQL Specification, the official standard that defines the query language, the type system and the execution semantics, maintained under the GraphQL Foundation.
- The official GraphQL documentation at graphql.org, which covers the schema definition language, queries, mutations, subscriptions and best practices with worked examples.
- The GraphQL Foundation, part of the Linux Foundation, which governs the specification and coordinates the open-source ecosystem around it.
Final thoughts
GraphQL is best understood not as a rival to REST but as an answer to a specific, well-defined problem: clients that need flexible, precisely-shaped data from potentially many sources, without the waste of over-fetching or the latency of under-fetching. It solves that problem elegantly with one endpoint, a typed schema, and a query language where the response mirrors the request. Where that problem is your problem, GraphQL is a genuinely excellent tool.
The honest counterweight is that GraphQL trades REST's free HTTP caching and its low complexity for that flexibility, and it moves the N+1 problem from the network into the server where you have to batch it away deliberately. Adopt it for the right reasons: multiple divergent clients, backend aggregation, fast-moving frontends, network-sensitive mobile experiences. Skip it for a small, stable, cache-friendly API where REST already serves you well. As with every choice in integration architecture, the skill is not in knowing which technology is fashionable but in matching the tool to the shape of the problem in front of you.
Choosing an API style for a real integration?
Independent, vendor-neutral advice on API and integration architecture: GraphQL, REST, gRPC, messaging and events, and where each belongs in your stack. 22+ years across ERP, EAM, CAFM and enterprise integration. No platform margins, no reseller arrangements.
Book a conversationRelated reading: Enterprise system integration explained, REST API explained, REST versus SOAP, What is gRPC, Business Central APIs and integrations.
Muhammad Abbas
CMMS / CAFM Manager & Enterprise Integration Specialist · 22+ years across ERP, EAM, CAFM and enterprise integration.
Work with me