Blog
2026-09-13 · GRAPHQL · 9 MIN

GraphQL vs REST API: Designing High-Performance Enterprise Data Layers

REST stops scaling once a single view needs several HTTP round trips and dozens of fields nobody renders. We break GraphQL down: the N+1 problem, DataLoader, caching, security.

IN THIS ARTICLE

Modern digital ecosystems depend on the efficient exchange of data between a wide range of client applications (web frontends, iOS/Android mobile apps, smartwatches) and a distributed backend architecture. For over a decade, the undisputed standard for building programming interfaces was the REST paradigm (Representational State Transfer). Simple and dependable as it is, REST starts to create serious engineering problems in complex, heavily relational Enterprise-class systems. Network overhead, the need to fire several HTTP requests at different endpoints just to render a single view, and shipping tonnes of redundant data are a common form of technical debt across many platforms. The answer to those limitations is GraphQL — a standard created by Meta (Facebook) and developed by the community. At odysse.io we do not treat technology dogmatically — we choose the API architecture on the basis of rigorous performance testing, keeping data transfer to a minimum and holding to the highest standard of code quality.

GraphQL redefines client–server communication by introducing a strongly typed query language in which the frontend decides precisely which data structures it needs at any given moment. In this part of our compendium we will analyse the fundamental architectural differences between GraphQL and REST, look closely at overfetching and underfetching, and go deep into the anatomy of queries and mutations in GraphQL engines.

01Architectural philosophy: rigid resources versus a flexible graph of connections

To see the fundamental difference between the two approaches, you have to look at how each one models data. REST architecture rests on the concept of resources, each of which has its own unique, unambiguous URL. To fetch a user profile, we query the /api/users/1 endpoint. To find out about their orders, we move on to /api/users/1/orders, and the details of a specific product from that order require a hit against /api/products/99.

GraphQL rejects the idea of multiple endpoints in favour of a single, fixed access point — usually just /graphql. The whole system is built on a data graph. Instead of thinking about data as isolated tables or rigid files, GraphQL treats the application as a dense network of connected nodes and edges. The client sends the server a single query describing the exact shape and depth of the relations it wants to pull, and the server responds with a perfectly matched JSON object.

02Anatomy of the problem: overfetching and underfetching at microservice scale

When designing mobile applications or advanced dashboard-style panels, frontend engineers fight constantly to limit battery drain and data usage, particularly over unstable network connections (3G/4G). Classic REST creates two serious performance problems here:

  • Overfetching (redundant data) — the REST endpoint /api/users/1 returns a complete user object containing 50 fields: everything from a first name, through login history, all the way to session tokens and internal database metadata. If all we need to show on a phone screen in a list view is the avatar and the login, the remaining 95% of the downloaded data is pure network overhead (payload bloat), which needlessly loads the connection and slows down parsing in the client application.
  • Underfetching (insufficient data) — the reverse situation. To render a profile view with a list of orders and a delivery status, the application has to make 3 or 4 separate HTTP requests in sequence (waterfall requests). Every request carries the cost of a TCP/TLS handshake, which drastically stretches Time to Interactive (TTI) and hurts the page's Core Web Vitals.

GraphQL eliminates these anomalies entirely. The frontend writes a declarative query — it asks for exactly those 2 fields from the user table, and in that same query it can nest the orders relation together with product names. Network transfer is reduced to its theoretical minimum.

03Anatomy of a GraphQL engine: schemas, queries and mutations

The foundation of security and stability in GraphQL is strong typing based on a schema defined using SDL (Schema Definition Language). The schema acts as a hard contract between frontend and backend, removing the need to maintain documentation in external tools (such as Swagger in REST) — GraphQL documents itself through introspection.

GraphQL operations come in two main kinds (not counting real-time subscriptions):

Queries — the equivalent of GET

These are used purely to read data. The frontend sends a readable structure that looks like JSON, but without the values:

graphql
query GetUserProfile {
  user(id: "1") {
    login
    avatarUrl
    orders(limit: 5) {
      totalAmount
      productName
    }
  }
}

Mutations — the equivalent of POST, PUT and DELETE

These handle every change of state on the server side: creating a record, updating a field, deleting an entity. Syntactically they resemble queries, but the engine executes them sequentially rather than in parallel, which keeps the order of writes within a single operation predictable. A mutation also declares the shape of its response, so the same request both writes the data and returns the updated object — with no extra round trip to the server:

graphql
mutation UpdateUserLogin {
  updateUser(id: "1", login: "new-login") {
    login
    updatedAt
  }
}

04Solving the N+1 performance problem: the DataLoader mechanism

For all the advantages of its flexibility, GraphQL architecture carries a serious performance risk at the database level, known as the N+1 query problem. It shows up whenever we fetch a list of objects (say, 10 orders) together with their relations (say, the authors of those orders). If the GraphQL engine maps fields naively through its resolvers, it will first fetch the list of 10 orders (1 query against the SQL database) and then, for each order separately, run a query for the author (N database queries). The result is that instead of one optimised query with a JOIN, the backend hits the database 11 times — which at scale produces drastic latency and cripples the infrastructure.

At odysse.io we eliminate this problem without exception, using the DataLoader mechanism (originally created by the team at Facebook). DataLoader acts as an intermediate batching and caching layer between the GraphQL resolvers and the database. Rather than firing SQL queries immediately for each field individually, DataLoader asynchronously collects — batches — every request for identifiers within a given event loop cycle. It then executes exactly one optimised bulk query (for example using a WHERE id IN (...) clause), drastically reducing database load and restoring the system to optimal linear performance.

05The caching challenge: network proxies versus client-side caching

In REST API architecture, caching is remarkably simple and implemented natively at the level of the HTTP protocol itself. Because every resource has its own unique URL, proxy servers, CDNs (Cloudflare, for instance) and web browsers can all cache server responses flawlessly on the basis of standard headers such as Cache-Control and ETag.

With GraphQL the situation is considerably more complicated. Because every request — queries and mutations alike — is sent as a POST to the same URL (/graphql), global HTTP network caching becomes useless. We solve this challenge in two ways:

  • Client-side caching (normalized client cache) — advanced client libraries such as Apollo Client and Urql implement a normalized cache inside the frontend application itself. Every returned object carrying a unique ID is broken apart and stored in a flat data structure. If another query in the application needs the same data, it is served instantly from the device's RAM without any network request at all.
  • Persisted queries — a technique that maps long GraphQL queries to unique cryptographic hashes on the server side. This makes it possible to send queries via GET using the hash, which restores full compatibility with traditional CDNs and enables edge caching.

06Hardening a GraphQL endpoint against DDoS attacks

The flexibility of GraphQL, which hands enormous power to frontend developers, is at the same time a serious security risk if the backend has not been hardened properly. A malicious user or bot can construct a destructive nested query (a deeply nested query or cyclic query) that forces the server into endlessly resolving relations (user → orders → user → orders…). Executing a request like that can consume the server's entire RAM in an instant and bring the system down — a denial-of-service (DoS) attack.

To protect our systems' endpoints effectively, odysse.io engineers deploy layered defence mechanisms:

  • Query depth limiting — the GraphQL parser analyses the structure of the query tree before executing it. If the nesting level of relations exceeds a safe, predetermined value (a maximum of 5 levels, say), the query is rejected immediately with a validation error.
  • Query complexity cost — assigning a numeric "weight" to individual fields and relations (a simple text field = 1 point, a database relation = 5 points, for example). If the total computational cost of a query exceeds the limit defined for a given API token, the system blocks the operation.

07Measurable business benefits for your organisation

Deploying GraphQL architecture on the Enterprise projects odysse.io delivers translates into concrete optimisation of operational and business costs:

  • Dramatic savings in transfer and battery life — eliminating overfetching means mobile applications run far more smoothly and consume less data, which sharply raises end-user satisfaction (especially while travelling or on a weak signal).
  • Faster product development (developer velocity) — once the SDL schema is defined, the frontend team can build new views and pull additional fields on its own, without constantly pulling backend developers in to write new REST endpoints.
  • Optimised cloud resources — thanks to batching (DataLoader) and smart client-server caching, databases are not flooded with redundant queries, which brings down the bill for AWS or Google Cloud infrastructure.

08In closing: choosing an API architecture deliberately

GraphQL and REST API are both powerful paradigms; they do not exclude one another, they simply serve different ends.

REST remains an excellent choice for simple CRUD systems, public APIs and file transfer. GraphQL, on the other hand, wins outright wherever the client application works with complex, densely connected data relations and the network performance of the frontend is the key to succeeding in the market.


At odysse.io we do not believe in universal solutions. We design data layers tailored to the specifics of your business, holding to the highest standards of security, query optimisation and uncompromising speed. Get in touch and we will analyse your system's architecture and implement a data exchange model that will speed up your frontend and take the load off your infrastructure.

GraphQL vs REST API: Designing High-Performance Enterprise Data Layers — Odysse Blog