Summarized using AI

The Joy and Agony of GraphQL

Stefan Kanev • April 26, 2025 • Sofia, Bulgaria • Talk

In his talk titled "The Joy and Agony of GraphQL" at the Balkan Ruby 2025 event, Stefan Kanev, CTO of Dext, shares a comprehensive exploration of GraphQL, its architecture, and its advantages and challenges compared to REST APIs.

Key Points Discussed:

  • Introduction to GraphQL:

    • GraphQL is presented as an alternative to REST for building APIs, characterized by a single endpoint and the ability to specify exactly what data is returned.
    • The structure of GraphQL queries is compared to a graph, where types are nodes and resolvers are the edges connecting them.
  • Metaphors Used to Explain GraphQL:

    • Kanev likens GraphQL to an onion; it has layers (complexity), can be enjoyable (if liked), and can make you “cry” (frustrating experiences).
    • He humorously aims to provide a "taste" of the functionality GraphQL offers.
  • GraphQL's Implementation Challenges:

    • The transition from existing legacy systems to GraphQL introduces complexity, especially when adding it to systems with existing codebases.
    • Common pitfalls discussed include the "N+1 queries" problem, where multiple database queries can cause performance issues. Kanev explains how lazy loading and batch processing can help mitigate this issue.
  • Implementation in Rails and Examples:

    • He walks through the implementation of GraphQL in a Rails application, detailing the code for defining query types, resolvers, and GraphQL schemas.
    • Real-world examples include querying pull requests and their comments from a GitHub-like API, showcasing how the structure of GraphQL queries leads to clearer and more controlled data retrieval.
  • Performance Optimization Techniques:

    • Kanev emphasizes the need for efficient data loading techniques, such as using the Data Loader pattern to batch requests and reduce database queries.
    • He highlights the importance of understanding databases such as Postgres and utilizing features like lateral joins for advanced querying.
  • Conclusions on Using GraphQL:

    • The decision to use GraphQL should be based on the specific use case; it can provide significant benefits but also has its complexities and potential drawbacks.
    • Kanev concludes with the recognition that understanding and implementing GraphQL requires a good grasp of underlying technologies and thoughtful consideration of developer experience.*

In conclusion, Kanev’s talk offers a deep dive into GraphQL, illustrating its capabilities and potential headaches while advocating for a thoughtful balance between developer tools and project needs.

The Joy and Agony of GraphQL
Stefan Kanev • Sofia, Bulgaria • Talk

Date: April 26, 2025
Published: May 05, 2025
Announced: unknown

Stefan Kanev is a CTO at Dext and a host of https://tilde-slash.fm.

Balkan Ruby 2025

00:00:00.000 Our next speaker is the CTO at Dext, and he has been doing software development since he was a child. Professionally, he has over 20 years of experience. Over the years, he has done a lot of things, including teaching at the university here in Sofia, which is amazing because he has supported the Ruby community.
00:00:06.400 He also has a podcast that he hasn't posted to in about six years. I couldn't find it on Spotify, and he recently started streaming on Twitch, so you can check that out.
00:00:14.160 And yes, he's my boss, so please be kind to him.
00:00:26.680 Thank you. Can you hear me? Let's set the record straight: I'm not Gin's boss; I'm the boss of Gin's boss.
00:00:33.760 But I’m not here to talk about reporting lines; I’m here to talk about GraphQL. What better way to start than with a bit of audience participation? Can you raise your hands if you've ever looked in the general direction of GraphQL? Keep your hand up if you've actually used it in some way.
00:01:01.280 Now, keep your hand up if you’ve actually built or maintained a GraphQL API. Continue keeping it up if you think you’re very good at doing that.
00:01:14.000 Okay, I work with him. I know that's not correct! It's understandable because GraphQL is like an onion, and bear with me, I've thought hard about this; it's a very good metaphor. In fact, it's a three-fold metaphor. The first way GraphQL is like an onion is that it can be really enjoyable. If you’re into onions, there’s quite a lot of good Indian cuisine that uses it, and it can be quite tasty.
00:01:57.640 Also, it has layers. I don't know how many times I thought I understood GraphQL, only to peel back a layer and discover more complexity. Finally, and most notoriously, it can make you cry. So, my goal today is to give you a taste of the 'French onion soup' of GraphQL and some tips on how to make it nice.
00:02:56.879 By the way, Yaroslav, is he here? I made this image with ChatGPT. Oh wow, my job is safe! So, I'm Stefan, and here’s how I look on the internet and in real life.
00:03:21.280 Now, there’s a bit of good news and a bit of bad news. The bad news is that everything here is in Bulgarian. The good news is that I haven't posted on any of those things for ages, so you’re not missing out on anything. In fact, speaking of YouTube, the last video I tried to make was on Haskell, but I was too lazy to finish it.
00:03:38.840 You're not supposed to laugh or clap because if you didn’t like that joke, you’re really going to hate the next one.
00:03:57.440 Anyway, I have been using GraphQL since 2016, pretty much when it first came out. I want to start with my angle on GraphQL and how I’m using it because there are many different ways in which one can use it.
00:04:05.599 First, I’m working on an information system with a complicated model, showing a lot of data and letting the user edit a lot of it. There are way too many tables involved. Some of those tables are small log tables, but it's still too many tables, and that's important because if you’re doing something much simpler, your experience is going to be different.
00:04:53.320 We’re also fetching all the data through PostgreSQL, compared to using GraphQL to pull data from other APIs and services in the same request. That's a different problem with a different set of interesting challenges. But it’s not the problem I've got.
00:05:30.720 We also added GraphQL much later over a bunch of legacy code. That’s important because if you start a new project today with GraphQL, you can write Ruby in a way to avoid some of the pitfalls. But if you have a lot of Ruby code, you need to figure out how to make it work.
00:06:11.360 Finally, we have a React client that uses this API, an iOS client, and an Android client. Those are three separate codebases doing slightly different things.
00:06:18.320 If you’re not in that situation, why are you using GraphQL in the first place? You could use Hotwire, which I really like, or even use React Server Components, which I also like. But anyway, speaking of GraphQL, the story is very different on the client side versus the server.
00:06:55.439 I could give an entire talk on GraphQL on the client, but usually, you’re going to use Swift, JavaScript, or Kotlin on the client, while I’m going to focus on the server.
00:07:03.639 For those who haven't seen GraphQL, let me make a super quick introduction: it is a way to create APIs, and you do this instead of using REST, but it’s still over HTTP, and the responses are still in JSON.
00:07:45.880 Now, I know you don't need to do REST with JSON, but like everybody does REST in JSON, so in that way, they are similar. You also don’t need to use GraphQL over HTTP, but everyone does it, so close enough. What makes it different is that when you make a request, you use a custom language with its own grammar. You expose it by having only a single endpoint and only post to that endpoint.
00:08:48.400 You specify what you want to get back, and you only get the stuff you specify. The structure of the API is somewhat strongly typed, which means that while you can consume it from JavaScript and deal with JSON, if you think in TypeScript or Swift, you could generate types for your API responses.
00:09:29.840 GraphQL consists essentially of three components: queries, where all the fancy stuff is—where you fetch data—mutations, which are essentially remote procedure calls, and subscriptions, which are server-sent events. However, you can also perform a query on the result. The most interesting aspect of GraphQL is the queries where the complex part lies, and that’s what I’m going to focus on.
00:10:16.880 I need to show you some examples, and I will use the GitHub API. It may feel a bit GitHub-heavy because their API is quite robust. Every time I wonder how to do something, I look at their API and usually adopt how they’ve done it.
00:10:57.760 If I showed you a real GraphQL request to GitHub, it would introduce complexity that we don’t need for this narrative, so I’ll keep it GitHub-ish. Here’s a GraphQL request similar to the first one I ever wrote: we're fetching a pull request.
00:11:46.560 We know the pull request ID is one, and we want to get the ID, the title, all the comments of that pull request. For each comment, we want to get the body and some information about the user who made it, as well as the repository name. When you fire this request to the GraphQL API, you get back a JSON structure that closely mirrors the request you made.
00:12:36.160 You can see that it contains the pull request, the ID, the title, an array of comments, and the users in those comments. If you don’t select all those fields, for example, if you omit some fields, you will only receive a smaller response.
00:12:51.400 Now, how do we implement something like that in Rails? First, we start with an Active Record model representing a user, a comment, a pull request, and a repository. There are a few associations, and we know what they mean if you’ve used GitHub.
00:13:42.359 Next, we need to add the GraphQL gem, which helps you build an API. I have a lot to say about that gem: it was really cumbersome seven years ago, but it’s actually quite good today, though still not as smooth as Rails. It has massively improved and makes life a lot easier.
00:14:35.400 The first thing you’ll do is define your query type, which is the entry point where you can start your request. Here, we define the field pull request, indicating it will take an ID, and will return a pull request type which we’ll look into shortly.
00:15:15.640 When the user selects this field, we want to execute Ruby code to fetch the data. We call these resolvers, which is basically what you need to implement this part of the query.
00:15:52.000 Now, moving to the pull request type—this will give part of the query. It will get instantiated with the Active Record object returned in the previous step. Here we have the fields ID and title. They are just methods on Active Record, so we don’t need to do anything special about them. There are also associations, where we don’t need to do much either, but we can write resolvers.
00:16:43.200 When we want to fetch all the comments, we should select those comments linked to this pull request, possibly order them for stability. We want to select the repository as well.
00:17:36.000 The story of the comment type is similar. We filter the comments for the user using a user find operation on the user ID, and here we get the body from the comment.
00:18:25.599 Finally, for completeness, here's the user type from which you get the pull requests. All of this is typed, so you will have a GraphQL schema that describes all the types of your entry points, which is the query, and every other type you can obtain from the query. You don’t need to manually write that; GraphQL Ruby will generate this for you from your Ruby code.
00:19:25.440 At this stage, you might wonder why GraphQL is called GraphQL. If we draw the schema in this way, we can actually see it is a graph. I know it sounds very computer science-y, but all the types are the nodes, and all the resolvers are the edges between those nodes.
00:20:02.000 So at this point, you are probably wondering, where are the tears? I promised you tears. Let’s see how one of those things gets resolved. Here’s the query, and let’s see what happens when we run it.
00:20:57.440 Execution will start here. We need to make a query to the database. That query will retrieve the pull request by ID, and then we’ll select all comments with that pull request. Then we will query each comment for its user, resulting in an additional query for each user, leading to multiple queries and causing the N+1 problem.
00:21:36.000 Now, obviously, we don’t want to do that. Instead, we need to do what we’ve done in Rails for ages. I know you're going to say we could just use includes or preload, which I think is a bit new. The question is, where do we put this? Returning to this comment type, we don’t have an array of comments; we just have a single comment where the user is already loaded.
00:22:38.480 We need to resolve this field before we move on to the next one. This presents a challenge because at this stage, we need to specify what to do to find this user without knowing other users are involved, which leads us back to the N+1 problem.
00:23:20.080 If you think about it, we’ve got a complicated problem. Some might say it’s quite scientific, and yes, we could tackle it using Haskell. I know it sounds like a lazy approach, but allow me to elaborate.
00:24:02.239 With Haskell, we can design our code to handle lazy loading of requests. Here’s how it goes: instead of returning a result right away, we could just indicate that we need to fetch this user. We can accumulate all the required IDs across queries and, afterward, we resolve them all together in one pass.
00:24:53.440 There are ways to do this using GraphQL's built-in lazy load mechanism. However, this requires additional consideration around promise-based code. I do not advocate for GraphQL batch processing and will instead focus on a proper implementation.
00:25:45.840 Now, moving on to the challenges we face when implementing associations. Ideally, we'd still use the concepts from the Rails framework. Rails offers various tools to manage associations effectively.
00:26:41.280 Another key point is GraphQL’s introduction of a data loader. This method allows you to batch your network requests to fetch your responses from a stored GraphQL API. Recently, they added more methods to enhance efficiency, enabling you to avoid unnecessary requests.
00:27:36.960 In essence, you can effectively use the data loader to reduce overhead by loading data asynchronously. All you must do is call it upon your model, which will help you achieve lazy loading without the inconvenience of managing complex promises in JavaScript style.
00:28:37.440 Now that we’re equipped with data loaders, let’s recap how to maintain consistency while implementing nested queries. The libraries we choose often determine success in avoiding the N+1 problem, especially with GraphQL.
00:29:22.640 The big question remains: when should you use GraphQL? The boring answer is: it depends on your project. It can be highly beneficial in cases like ours but also overwhelming for simpler projects.
00:30:00.080 Ultimately, engaging with complex libraries requires a willingness to dive into intricate systems, akin to enjoying onion soup. In the long run, if you appreciate the challenges, it will provide joy; if not, you might want to find someone who does.
00:30:31.760 In summary, I’ve presented various aspects of how to work with GraphQL, alongside the challenges you might face. I encourage anyone dealing with these issues to reach out; perhaps together, we can overcome some of these hurdles.
00:31:04.000 Thank you for your attention, and I look forward to any questions you may have!
Explore all talks recorded at Balkan Ruby 2025
+5