ionTrails Modernization
API Architecture

GraphQL quick-and-dirty

Published on October 15, 2020 by Viliam Simko · Updated October 26, 2026

Originally drafted on October 15, 2020. Revised and expanded in October 2026.

GraphQL is a query language for APIs that allows clients to request the data they need.

It was developed at Facebook to address limitations encountered with REST APIs. Different clients—web applications, mobile apps, or other services—often need different representations of the same underlying data. With REST, this can lead to overfetching, underfetching, or multiple requests to retrieve related resources.

GraphQL offers a different approach. Instead of relying exclusively on predefined resource representations, clients describe the shape of the data they want. The server resolves the query and returns a response matching that selection.

The idea is compelling. A single API can serve different clients without necessarily requiring a growing collection of specialized endpoints. The type system also provides a contract that can support validation, documentation, and tooling.

GraphQL is technology-agnostic, with implementations available for many programming languages. And despite the name, GraphQL is neither tied to Facebook nor dependent on a graph database.

A concrete example

Imagine a client needs a user’s name and the titles of their two latest posts, along with one comment on each post. A GraphQL query can describe that shape in one place:

query UserAndRecentPosts {
  user(id: "42") {
    name
    posts(first: 2) {
      title
      comments(first: 1) {
        body
      }
    }
  }
}

The response follows the requested structure:

{
  "data": {
    "user": {
      "name": "Ada Lovelace",
      "posts": [
        {
          "title": "Notes on computation",
          "comments": [{ "body": "A fascinating read." }]
        },
        {
          "title": "A new algorithm",
          "comments": [{ "body": "Looking forward to more." }]
        }
      ]
    }
  }
}

With a REST API, the same screen might require requests to a user endpoint, a posts endpoint, and comment endpoints—or a purpose-built endpoint that returns the view the client needs. The exact design depends on the API; GraphQL does not automatically make the work faster or reduce the server’s work to a single database operation. It makes the requested response shape explicit and lets the server resolve it.

Why GraphQL looked attractive to me in 2020

When I first explored GraphQL in 2020, its flexibility was the main attraction. I was genuinely excited by the possibility of letting different clients ask for the data they needed instead of shaping every response around a fixed endpoint. It seemed like a practical answer to familiar REST limitations, not just a new technology for its own sake.

I was also interested in the development workflow. I worried that maintaining a schema and its resolver implementation separately could introduce duplication and drift as a project evolved. Code-first tools such as NexusJS looked promising because they offered a way to define the API in application code and generate a schema from those definitions. At the time, that felt like a useful way to keep the contract and implementation close together.

That enthusiasm is part of this article’s starting point. I did not think GraphQL was automatically better in every situation; I thought its flexibility and tooling might make certain API designs easier to build and evolve. Looking back, those advantages are still real. What has changed is how carefully I weigh the cost of getting them.

REST is not the problem it is sometimes made out to be

A conventional REST API exposes resources through URLs such as /users or /posts. Clients interact with them using HTTP methods and receive representations, commonly JSON.

This model is straightforward, familiar, and well supported by existing infrastructure. It also provides useful defaults: HTTP methods communicate intent, status codes describe outcomes, and resource URLs give clients predictable places to look.

Of course, REST has limitations. A client might need several requests to assemble a view, or receive more data than it needs. Designing an API that serves different clients well requires thought, and poorly designed REST APIs can become awkward collections of endpoints with inconsistent conventions.

GraphQL addresses some of these issues by giving clients more control over response shapes and related data.

But there is an important distinction between a limitation of REST itself and a limitation of a particular REST API design. Some problems genuinely benefit from GraphQL. Others can be solved by designing better resource representations, introducing a purpose-built endpoint, or simply accepting a few additional requests.

Not every inefficiency requires a new abstraction.

Flexibility is not free

GraphQL moves some decisions from the API designer to the client. Rather than defining every response shape in advance, the server allows clients to request different combinations of fields.

That flexibility has consequences for the server.

A GraphQL implementation needs to resolve requested fields, retrieve data, enforce authorization, and handle errors. A query that looks compact on the client can trigger many database operations or calls to other services. Performance therefore depends on how queries are executed, not just on how concise they look.

Caching requires consideration, too. GraphQL responses can be cached, but caching arbitrary queries and their results is not always as straightforward as caching predictable resource representations over HTTP. The right solution depends on the API, clients, infrastructure, and consistency requirements.

None of this makes GraphQL inherently slow or difficult to operate. These are engineering problems with established solutions. The question is whether those solutions are justified by the capabilities the API needs.

A flexible interface can reduce complexity for clients while introducing complexity on the server. The complexity has not necessarily disappeared; it may simply have moved somewhere else.

That is the trade-off I now pay more attention to.

The everyday cost of complexity

Both REST and GraphQL can be inspected with curl. Neither requires a graphical client just to understand a request. But when working with a straightforward REST API, I often find the interaction more direct: the URL identifies a resource, the HTTP method expresses the operation, and the response shows the result.

GraphQL introduces a query document and its own request structure. This is not particularly complicated, but it is another layer to understand when writing requests, inspecting responses, and investigating unexpected behaviour.

The difference can be small in any individual interaction. Over time, however, these small frictions accumulate.

The same applies to implementation. A simple REST endpoint can often be added directly to an existing backend, sometimes without introducing any additional dependencies. A GraphQL server requires the machinery needed to parse and execute queries, expose a schema, and resolve requested fields.

Libraries and frameworks can make this machinery convenient. That convenience matters, but it does not make the machinery free. There is still code to understand, configuration to maintain, behaviour to debug, and an abstraction to keep consistent with the underlying system.

This is not an argument that REST is suitable only for small applications. The simplicity of a resource-oriented API can remain valuable in a substantial system. Conversely, a complex domain with many different consumers may benefit greatly from GraphQL.

The size of the project does not decide the question. The shape of the problem does.

For me, the lesson is that architectural complexity should be evaluated across the whole development lifecycle, not just by how elegant the initial implementation looks.

Code-first vs. schema-first

My original 2020 draft also focused on the choice between code-first and schema-first GraphQL development.

In a schema-first approach, the GraphQL schema is defined explicitly, and the implementation is built around it. In a code-first approach, types and fields are defined in application code, with the schema generated from those definitions.

I was concerned that maintaining a schema and its resolver implementation separately could introduce duplication and drift as a project evolved. Frameworks such as NexusJS were interesting because they offered a way to define the API in code and generate the schema from those definitions.

The concern was reasonable, but the distinction is not as simple as one approach being inherently better. Schema-first development can provide a clear, reviewable contract. Code-first development can reduce duplication and keep related definitions together. Either can become difficult to maintain if the workflow is poorly designed.

The broader principle matters more than the specific framework: abstractions should reduce the total cost of maintaining a system, not merely make the first version easier to write.

This principle applies to REST as well. API contracts, validation, documentation, and implementation all need to remain aligned, regardless of the technology used.

What I ask before choosing an API architecture

Today, I would start with the requirements rather than the technology.

How many different clients will consume the API? Do they genuinely need different combinations of nested data, or would a handful of well-designed resource representations suffice? How frequently do their data requirements change? What are the caching, authorization, performance, and operational constraints? How much complexity will the team have to maintain?

These questions help establish whether GraphQL’s flexibility solves a real problem.

GraphQL is a strong candidate when clients need substantially different views of interconnected data, when those requirements evolve frequently, or when a well-designed query interface meaningfully simplifies client development.

REST is a strong candidate when the domain maps naturally to resources and operations, when response shapes are reasonably stable, and when straightforward HTTP semantics provide a sufficient contract. It can also be the pragmatic choice when the team values a direct implementation and has no compelling reason to introduce another abstraction.

There is no requirement to choose one approach for every use case in a system. REST and GraphQL can coexist where their respective strengths make sense.

The objective is not to maximize flexibility. It is to provide a good interface at an acceptable total cost.

What I would choose today

In 2020, I focused on what GraphQL made possible. Today, I also pay closer attention to what it makes necessary.

I have come to appreciate the simplicity of REST more deeply: how directly an endpoint can be implemented, how easily a request can be reproduced with curl, and how little additional machinery may be needed to deliver a useful API.

Those qualities are easy to overlook when comparing technologies by their feature sets. Yet they affect the everyday cost of building and maintaining software.

I have not concluded that REST is always better. GraphQL remains a useful solution to real problems, and its flexibility can be worth the additional complexity. The mistake would be to assume that flexibility is automatically an advantage, regardless of whether a system needs it.

The same principle applies to architecture more broadly. An abstraction earns its place when the problems it solves outweigh the costs it introduces.

REST vs. GraphQL is, of course, a matter of context. Each technology has its place. The important thing is to understand what you are optimizing for—and to remember that simplicity is a feature, too.

← Back to all articles