Why GraphQL?
I am convinced, and I want to convince you too: Any reasonably extensive REST API will turn into a poor imitation of a GraphQL API.
Just try writing an API to display posts in a blog. You'll probably design the following methods:
GET allPosts() GET post(postId) GET comments(postId) It won't be long before allPosts() turns into something like this:
GET allPosts(skip, take, filterFields[], filterValues[], fieldsToInclude[]) To avoid needing a separate round trip for loading comments, you might also modify the post() method a bit:
GET post(postId, commentsSkip, commentsTake) Most APIs in the enterprise environment look somewhat like this. Rather individual, rarely uniform. Is there a way to integrate these recurring demands for API flexibility into a standard from the start? Clever developers at Facebook thought about how to streamline and simplify API designs. The result is GraphQL - the next evolutionary step in APIs.
What is GraphQL?
It's important to note that GraphQL is just a standard. While there are libraries like Apollo where parts are conveniently pre-programmed, this is of course technology-dependent.
GraphQL is a language for querying APIs with a "query language." On the client side, an API query looks more like a "database query," while a classic REST call resembles a method call. This may seem marginal at first, but it represents a significant change in mindset regarding how we interact with APIs.
A GraphQL query might look like this:
{posts (author:"alice", skip: 10, take: 10) {
id,
author,
content,
headline,
tags,
comments {
author,
comment,
}
}
} So here we fetch posts 11 to 20 from "alice" and select the fields "id," "author," "content," "headline," and "tags." At the same time, we load comments for the posts, and from the comments, we load the fields "Author" and "comment."
GraphQL can be seen as a generic "data access layer." Where the comments come from is completely irrelevant and is programmed in the backend. If we have a document-based database, the comments might be stored directly in the post document. If we have a relational DB, the comments might be in a separate table. Theoretically, the comments could also be stored in a text file or retrieved via another API from a third-party system - from the perspective of the querying code, it doesn't matter; we only see that there are comments and that they apparently have a relationship with the posts.
Advantages and Disadvantages Compared to REST APIs
Optimized data queries from the client perspective
Never again overfetching and underfetching.
In the GraphQL query, we explicitly specify which fields, sub-entities, and fields of the sub-entities we want to query. In any reasonably well-implemented backend, only the requested fields will actually be delivered. This makes it easy to use the same interface for multiple queries of varying granularity.
For example, a post overview page might only need a headline and the tags, while the detail page of a post needs to query the full content along with comments. From a GraphQL perspective, you can query "posts" in both cases - only the list of queried fields changes.
Less effort in the backend due to stronger decoupling
The same concept has significant, often overlooked implications in the backend: As an API developer, I can define a large number of fields as return values in my API. I don't have to worry about performance because only the fields requested will be returned, and only those sub-entities that the frontend actually needs will be queried. (Assuming proper programming in the backend...)
This has very simple but powerful implications: If, for example, the "tags" are not needed in the overview page but are later requested by users, a purely frontend adjustment is sufficient. The backend does not need to be extended or redeployed. This can potentially save massive effort and complexity in the long run.
Built-in parallelism
Let's say we write a complex GraphQL query that queries multiple sub-entities, where individual sub-entities have different data sources. Posts and comments might be in a database. Other information might need to be retrieved from another server, again as an API, or might be stored as files somewhere on a network drive connected to the server.
Good GraphQL frameworks provide convenient mechanisms for handling sub-entity queries. In Apollo under NestJS, loading a post along with comments and revisions might look like this:
@Query(returns => Post)
async post (@Args('id', { type: () => Int }) id: number): Promise<Post> {
return // Lade Post aus der DB
}
@ResolveField()
async comments(@Parent() post: Post) {
return // Lade Comments mit comment.postId == post.Id
}
@ResolveField()
async revisions(@Parent() post: Post) {
return // Lade Revisions mit revision.postId == post.Id
} Apollo automatically checks whether comments and/or revisions are queried in the GraphQL query, and if so, those sub-entities are resolved with the respective async method. And this happens in parallel by default.
This is just one way to approach the issue. In a relational database, you might want to work without ResolveField and directly execute a JOIN. In other scenarios, especially when data needs to be loaded from various data sources, a data join via ResolveField is a very convenient/simple and performant solution, as the data sources are queried in parallel out-of-the-box.
Conveniently, the results of the queries are then automatically inserted into the parent model (here: Post) by Apollo.
Deprecation Feature
Especially with large, long-lived APIs, good management of deprecation and evolution is often underestimated. IT systems are constantly evolving, and I don't know of a sufficiently complex and long-lived API that hasn't had issues with deprecation. The usual workaround is that multiple different versions of APIs must be operated in parallel (at least temporarily).
The GraphQL standard offers its own @deprecated directive, which allows you to easily indicate which fields are deprecated (with an optional reason).
Most GraphQL clients read this directive and issue a warning if a deprecated field is used.
This ideally prevents the need to operate multiple (different) versions of APIs in parallel. It remains a single API that is continuously modified/expanded over time.
Subscription Feature
The GraphQL standard also defines a subscription feature for push notifications. This means that no additional technology is needed from the client's perspective for such functionality.
GraphQL as a Central Control Point / Data Query Layer
This was hinted at earlier, but here it is again at a glance: GraphQL changes how we interpret an API. We no longer see an API (only) as a collection of methods that return something. Rather, we see a GraphQL API as a "data access layer":
- Syntax with a strong focus on data objects, their fields, and their relationships to each other
- Unified view of data from various data sources/systems
- Performant due to query parallelism and easy control over which data is actually transmitted over the network
- The frontend/client does not need to know which data sources individual fields or sub-entities come from. The frontend simply queries what it needs - the backend takes care of querying the appropriate sources.
- In general, you save round trips to the server, or you can design queries more easily than with REST to require fewer calls overall while still achieving good API design.
Typing for pleasant & safe development
Types are "first-class citizens" in GraphQL. This not only makes development safer but also more enjoyable. In most GraphQL IDEs, types are naturally considered and validated. Additionally, typing (especially of complex objects/models) enables very pleasant auto-completion features in the IDEs.
A few disadvantages & challenges
- Just like with REST APIs, you need to pay attention to proper authorization/authentication and parameter validation in GraphQL as well. Nothing prevents a client from creating a query like ...(skip:0, take: 1000000) This must be handled on the backend.
- "Too" complex queries are possible: Although performance is a strength of "properly" used GraphQL queries, there is also a pitfall: Clients now have the ability to issue complex queries that result in long loading times.
- The learning curve for GraphQL is currently considered steeper than that of REST APIs.
- The GraphQL community (and thus the number of examples, tutorials, etc.) is smaller than that of REST counterparts.
- "Overkill for simple apps": In researching this article, I came across statements suggesting that using GraphQL is more worthwhile for larger APIs. I personally do not share this opinion. With the current tooling and basic training, implementing a GraphQL API is not more complex or time-consuming than developing a REST API. However, you enjoy several advantages that become apparent, especially when the API is used, maintained, and expanded over time.
Training & Support for Implementation
Do you need support in designing or implementing a GraphQL interface? Or would you like to upgrade your know-how on this topic quickly through a workshop, training, or by jointly implementing a prototype? Contact us now via the following contact form and let's get started :)