<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Lucas Correia</title>
    <description>The latest articles on Forem by Lucas Correia (@tsirlucas).</description>
    <link>https://forem.com/tsirlucas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F360802%2Fe07970ca-ddac-49a8-99b7-9d2b1f55413d.jpeg</url>
      <title>Forem: Lucas Correia</title>
      <link>https://forem.com/tsirlucas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tsirlucas"/>
    <language>en</language>
    <item>
      <title>Contract Tests with TypeScript and OpenAPI Codegen</title>
      <dc:creator>Lucas Correia</dc:creator>
      <pubDate>Sat, 08 Feb 2025 17:22:40 +0000</pubDate>
      <link>https://forem.com/tsirlucas/contract-tests-with-typescript-and-openapi-codegen-4o7g</link>
      <guid>https://forem.com/tsirlucas/contract-tests-with-typescript-and-openapi-codegen-4o7g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;At Chili Piper, one of our ongoing challenges is ensuring our frontend applications stay in sync with backend changes.  &lt;/p&gt;

&lt;p&gt;For context: We utilize auto-generated clients via OpenAPI Codegen to maintain a strongly typed contract between frontend and backend.&lt;/p&gt;

&lt;p&gt;A recurring issue we've faced is the backend team deprecating an endpoint or field and removing it after a few weeks. Issue is that often, the frontend team doesn't stop using the deprecated field in time, leading to runtime failures upon deployment.&lt;/p&gt;

&lt;p&gt;This post outlines our approach to addressing this problem using contract tests with TypeScript and OpenAPI Codegen.&lt;/p&gt;




&lt;h2&gt;
  
  
  Minimal Setup for Contract Tests
&lt;/h2&gt;

&lt;p&gt;To set up contract tests effectively, it's essential to have a mechanism to store the currently deployed versions for each service or application.&lt;br&gt;&lt;br&gt;
In our case, we use a YAML file in one of our repositories. Deployments are triggered when a version is changed through a PR, thanks to our diligent SRE team.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps to Implement Contract Tests
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Determine the deployed versions&lt;/strong&gt;: Detect the versions of backend services currently deployed in the target environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate API clients dynamically&lt;/strong&gt;: Use OpenAPI Codegen to generate clients based on the detected backend versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checkout frontend versions&lt;/strong&gt;: For each frontend application, checkout the tag corresponding to the deployed version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Override API clients&lt;/strong&gt;: Replace the API clients in the frontend apps with the environment-based generated code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run TypeScript checks&lt;/strong&gt;: Use TypeScript to validate that no API contracts have been broken.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This process ensures that any breaking change in the backend will immediately fail the TypeScript checks, providing early feedback before deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;One downside of this approach is its computational expense.&lt;br&gt;&lt;br&gt;
With around &lt;strong&gt;15+ micro-frontend (MFE) applications&lt;/strong&gt;, running this check across all of them takes about &lt;strong&gt;10 minutes&lt;/strong&gt;, even when parallelized across &lt;strong&gt;three runners&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To improve performance, we implemented several optimizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skipping checks if there is no breaking change&lt;/strong&gt;: We use &lt;a href="https://github.com/OpenAPITools/openapi-diff" rel="noopener noreferrer"&gt;openapi-diff&lt;/a&gt; to detect whether the deployment contains a breaking change. If none is found, we skip the contract tests, reducing runtime to just &lt;strong&gt;2 minutes&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Caching dependencies&lt;/strong&gt;: By caching dependencies, we reduce redundant installations and builds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Grouping tests&lt;/strong&gt;: If multiple applications share the same monorepo tag, we run their checks together to avoid redundant executions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using TypeScript project references with incremental mode&lt;/strong&gt;: This allows us to reuse previously built code, improving build times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skipping Frontend checks&lt;/strong&gt;: Unlike backend deployments, we can't quickly determine if there is a breaking change in frontend applications (e.g. &lt;code&gt;openapi-diff&lt;/code&gt;). However, we &lt;strong&gt;can&lt;/strong&gt; detect version bumps and skip runs for unaltered apps.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the most interesting improvements was leveraging &lt;strong&gt;TypeScript project references with incremental mode&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
This allows us to reuse compiled output from previous runs, significantly reducing build times.&lt;/p&gt;

&lt;p&gt;But the best improvements were the ones were we would skip checks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Contract Tests Instead of Just Relying on &lt;code&gt;openapi-diff&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;openapi-diff&lt;/code&gt; helps detect breaking changes, it doesn’t tell us &lt;strong&gt;which frontend applications&lt;/strong&gt; are affected.&lt;br&gt;&lt;br&gt;
Contract tests allow us to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify &lt;strong&gt;which apps&lt;/strong&gt; are still using deprecated endpoints.&lt;/li&gt;
&lt;li&gt;Detect &lt;strong&gt;which apps need to be deployed together&lt;/strong&gt;.
For example, if a breaking change affects app A but not app B, we only need to deploy app A.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We considered using &lt;a href="https://pact.io/" rel="noopener noreferrer"&gt;Pact.io&lt;/a&gt;, but it requires maintaining separate contract files, which can be forgotten or become outdated.&lt;br&gt;&lt;br&gt;
With TypeScript, the contract is directly enforced by the code, eliminating the risk of stale contracts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling False Positives in TypeScript Project References
&lt;/h2&gt;

&lt;p&gt;While TypeScript project references help improve performance, they introduce a key issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript builds and reports errors for all files in a project, even unused ones&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Example&lt;/strong&gt;:
If &lt;strong&gt;app A&lt;/strong&gt; depends on a &lt;strong&gt;shared package B&lt;/strong&gt;, and package B has an error in an unused file, &lt;strong&gt;app A will still fail the build&lt;/strong&gt;, leading to false positives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially problematic because our automation &lt;strong&gt;blocks deployments&lt;/strong&gt;, and false positives would require us to &lt;strong&gt;deploy more apps than actually needed&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our Solution
&lt;/h3&gt;

&lt;p&gt;To mitigate this, we created &lt;strong&gt;a small wrapper around the TypeScript build tool&lt;/strong&gt; that suppresses errors from unused files, ensuring that only relevant issues cause test failures.&lt;/p&gt;

&lt;p&gt;You can check out our &lt;a href="https://gist.github.com/tsirlucas/ac28ba83bcd382bbcf8075aaaeccc508" rel="noopener noreferrer"&gt;Gist with the implementation here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We initially considered going even further by leveraging AST (Abstract Syntax Tree) analysis to &lt;strong&gt;only report type errors for actually imported code&lt;/strong&gt;. The current file-based filtering approach means that if function A has a type error but your app only uses function B, it will still report an error. With AST-based filtering, we could theoretically ignore errors from unimported code, reducing noise in the checks.&lt;/p&gt;

&lt;p&gt;However, after discussing this with a friend who has more experience with ASTs, he pointed out that this would essentially mean &lt;strong&gt;rewriting tree shaking&lt;/strong&gt;, which is far from trivial. Given the complexity involved, we opted to stick with file-based filtering for now. That said, we’re actively keeping an eye out for existing public implementations of tree shaking that we could potentially leverage, such as those used in &lt;strong&gt;Webpack or other bundlers&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By combining OpenAPI Codegen, TypeScript, and contract tests, we have built a robust solution to detect breaking API changes before they impact production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key takeaways:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;While the solution is in fact &lt;strong&gt;resource-intensive&lt;/strong&gt;, there are &lt;strong&gt;available workarounds&lt;/strong&gt; to mitigate this issue.&lt;/li&gt;
&lt;li&gt;This strategy for making contract tests is &lt;strong&gt;not the simplest to setup&lt;/strong&gt;, but it is &lt;strong&gt;maintenance-free&lt;/strong&gt;. You &lt;strong&gt;setup once and forget about it&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Whatever solution you pick, make sure it is something that will help developers more than block them. In my experience, &lt;strong&gt;false positives and long CI times are the main issues&lt;/strong&gt;, so make sure to address those.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're facing similar issues with breaking API changes, I highly recommend setting up a contract testing strategy tailored to your stack!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>openapi</category>
      <category>frontend</category>
      <category>backend</category>
    </item>
    <item>
      <title>Client-first RSC: A scalable solution for data aggregation</title>
      <dc:creator>Lucas Correia</dc:creator>
      <pubDate>Sun, 19 May 2024 21:06:07 +0000</pubDate>
      <link>https://forem.com/tsirlucas/client-first-rsc-a-scalable-solution-for-data-aggregation-4j34</link>
      <guid>https://forem.com/tsirlucas/client-first-rsc-a-scalable-solution-for-data-aggregation-4j34</guid>
      <description>&lt;h3&gt;
  
  
  RSC vs SSR
&lt;/h3&gt;

&lt;p&gt;First of all, we need to be in the same page on the fact that RSC and SSR are not the same thing. &lt;/p&gt;

&lt;p&gt;RSC is a strategy to run part of your react application on the server-side. Server components are isolated in the cloud and will never be sent to the client. Same is valid for server actions.&lt;/p&gt;

&lt;p&gt;SSR is a strategy for rendering your client components in the server. It generates HTML. Those components are sent and will run in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do you need SSR?
&lt;/h3&gt;

&lt;p&gt;SSR is really good if you have performance-critical applications. When you have pages that need to load on really poor connections. On really bad devices. If you do have this scenario, then you can drop this article and assume SSR is for you. Otherwise, keep reading.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSR scalability
&lt;/h3&gt;

&lt;p&gt;When trying to pitch next, one of the things that puts me in a corner is dealing with scalability. You're rendering your app on the server. You need computing power for that. You need to think about scalability, something you wouldnt need to worry if you were serving static assets instead.&lt;/p&gt;

&lt;p&gt;A co-worker gave me a scenario of thousands of parallel requests happening to our FE and asked how to solve this. At the time the only thing I could think of was horizontal scaling, but I believe this is not the answer he was expecting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Then why RSC?
&lt;/h3&gt;

&lt;p&gt;Now that I've explained myself on why I dont wanna use SSR, let me explain how Im planning to use RSC without facing similar issues: By avoid using server components.&lt;/p&gt;

&lt;p&gt;TBH, the only part that really interests me in the whole RSC hype is server actions. If you have a server component, when the user requests a page, you will be running some code in your cloud. This is what we are trying to avoid.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why server actions?
&lt;/h3&gt;

&lt;p&gt;The bauty of server actions is how simple it is for you to make a client-side function become a server-side function. All you gotta do is add a "use server" directive. A one-liner.&lt;/p&gt;

&lt;p&gt;My idea is to move the data aggregation layer into a function. The default behavior of the application Im trying to build is to delegate everything to the user machine, so we run that function in the client. &lt;/p&gt;

&lt;p&gt;If a specific page needs more performance (N+1, etc), we can just decide to run that function in the server. All it takes is a one-liner.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does it solve scalability?
&lt;/h3&gt;

&lt;p&gt;Considering that SSR is disabled, your server only needs to run server actions. But whats the difference when comparing against running SSR?&lt;/p&gt;

&lt;p&gt;The idea is that you wont start with server actions. Your functions will start in the browser, making requests to the microservices directly. Initially, your server will be in charge of serving static assets and thats all.&lt;/p&gt;

&lt;p&gt;As needed (and only if needed), you can make a specific page use a server action instead of a client function, or even enable SSR only for that page.&lt;/p&gt;

&lt;p&gt;Optimizing only what is absolutely needed helps alleviate server load and save on cloud costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not GraphQL?
&lt;/h3&gt;

&lt;p&gt;GraphQL is a really good BFF strategy that brings a lot to the table. I've consired using it myself a lot of times, but always decided to try some other approach because of its query language.&lt;/p&gt;

&lt;p&gt;What puts me off is that it requires me to follow a set of rules and structure my data in a way that complies with them.&lt;/p&gt;

&lt;p&gt;Im not 100% free on the way I consume the API cause I need to query things following another set of rules.&lt;/p&gt;

&lt;p&gt;If you start using it early, you will be able to allow the tool to dictate how your data will look like. If youre trying to adopt in an pre-existent project, its no easy thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solving the data aggregation problem
&lt;/h3&gt;

&lt;p&gt;I have a situation at work where our BE uses microservices. They dont provide a BFF, so FE needs to load entities from multiple services. You can get a glimpse of how things currently work in &lt;a href="https://dev.to/tsirlucas/uncovering-frontend-data-aggregation-our-encounter-with-bff-graphql-and-hydration-4l2i"&gt;this&lt;/a&gt; other article I wrote.&lt;/p&gt;

&lt;p&gt;The great thing about GraphQL is that if you configure things correctly, you dont need to write code to aggregate entities. Apart from performance and scalability, I want to be able to have this level of simplicity when making my requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maybe JSON:API?
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;I know... Its 2024 and Im talking about JSON:API...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What I want do accomplish here is to have a standard which allows me to know what relationships are available for a given entity.&lt;/p&gt;

&lt;p&gt;Not only that, but a standard means that I can expect data to be structured in a specific way. And with that, I can create generic solutions to accomplish the level of simplicity I want to have when aggregating data.&lt;/p&gt;

&lt;p&gt;Maybe not JSON:API, but I do need a standard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generic solutions and caching
&lt;/h3&gt;

&lt;p&gt;Im talking about aggregating data from multiple services. Multiple entities. But what about caching?&lt;/p&gt;

&lt;p&gt;If my client has a aggregated table with information from entity X and Y. If for some reason, entity Y updates in the same context of that table (e.g. in a modal). Do I want to refetch all the aggregated entities by calling that server function?&lt;/p&gt;

&lt;p&gt;The answer is no. The solution Im seeking has two steps: Fetching and querying. With that, When entity Y updates, I dont need to refetch the whole table. I can just re-query from a local storage. This can be done on client-side, which improves scalability even more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reinventing the wheel?
&lt;/h3&gt;

&lt;p&gt;While Im writing this, I can't stop thinking that Im trying to implement exactly what GraphQL is. I can even bet that there is some JSON:API client doing exactly what Im talking about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/emberjs/data/tree/main/packages/json-api" rel="noopener noreferrer"&gt;EmberData&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wopian/kitsu" rel="noopener noreferrer"&gt;kitsu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jun-sheaf/ts-japi" rel="noopener noreferrer"&gt;ts-japi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But, my goal is to have a solution that fits my needs at work. I have my reasons why I dont want to use GraphQL. My BE team may have their reasons on why they dont want to follow JSON:API strictly. My SRE team has their reasons on why they dont want to use SSR.&lt;/p&gt;

&lt;p&gt;In the end, the solution Im proposing here is made to solve the problems Im facing. Its a glove. There is no silver bullet.&lt;/p&gt;

&lt;h3&gt;
  
  
  To be continued
&lt;/h3&gt;

&lt;p&gt;I will be writing a separate post specifically on data management tools. Probably exploring a few of the JSON:API client libraries I shared. I wont go into details here for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Im not there yet. Im still trying to find the best tool for my use-case. &lt;/li&gt;
&lt;li&gt;I want to share a solid solution/strategy and not only opinions based in a few hours of research.&lt;/li&gt;
&lt;li&gt;This post is already too big.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Maybe with all the good things that RSC brings to the table its time to start looking back to check how it improves pre-existent tools.&lt;/p&gt;

&lt;p&gt;It opens a door for client-first solutions where you can gradually move things to the server as-needed. With the correct set of tools, you can accomplish similar performance as BFF in critical scenarios while having scalability and simplicity of static apps by default.&lt;/p&gt;

&lt;p&gt;We must go for simplicity first. We must optimize only when necessary. Optimization path should follow the first rule.&lt;/p&gt;

</description>
      <category>bff</category>
      <category>microservices</category>
      <category>nextjs</category>
      <category>react</category>
    </item>
    <item>
      <title>Integrating Dataloader with Concurrent React</title>
      <dc:creator>Lucas Correia</dc:creator>
      <pubDate>Tue, 22 Aug 2023 15:41:52 +0000</pubDate>
      <link>https://forem.com/tsirlucas/integrating-dataloader-with-concurrent-react-53h1</link>
      <guid>https://forem.com/tsirlucas/integrating-dataloader-with-concurrent-react-53h1</guid>
      <description>&lt;p&gt;You dont need to, but this is a follow up on my &lt;a href="https://dev.to/tsirlucas/uncovering-frontend-data-aggregation-our-encounter-with-bff-graphql-and-hydration-4l2i"&gt;previous post on data aggregation&lt;/a&gt;. The findings here happened while trying to implement a code sample for a presentation based on the post.&lt;/p&gt;

&lt;p&gt;Also, if you want a detailed deep dive on how the whole situation described here works under the hood, you may want to check &lt;a href="https://andreigatej.dev/blog/the-underlying-mechanisms-of-reacts-concurrent-mode/" rel="noopener noreferrer"&gt;this article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;TLDR: Add &lt;code&gt;batchScheduleFn: (callback) =&amp;gt; setTimeout(callback, 5)&lt;/code&gt; to the dataloader config. &lt;code&gt;batchScheduleFn: (callback) =&amp;gt;&lt;br&gt;
      unstable_scheduleCallback(unstable_IdlePriority, callback)&lt;/code&gt; if you dont mind about the unstable API.&lt;/p&gt;
&lt;h2&gt;
  
  
  Facing the issue
&lt;/h2&gt;

&lt;p&gt;First, lets take a quick look at the code in the following codesandbox.&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/reverent-feynman-8j8h7p"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We have a list of &lt;code&gt;Cards&lt;/code&gt;, each Card receives an &lt;code&gt;episode&lt;/code&gt; and requests a list of &lt;code&gt;characters&lt;/code&gt; that are related to the episode. Those requests are grouped into a single request thanks to dataloader. If you check your network tab, you should be able to see the only 2 requests happening: One for the list of episodes, and the other for all the characters existent in the episodes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyj5xb7cttqs0i9yx72yw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyj5xb7cttqs0i9yx72yw.png" alt="Image description" width="754" height="96"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lets say I'd like to use Suspense. Refactor it a little bit and we will end up with the following codesandbox:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/condescending-firefly-vgdygr"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Now, if you have a look at your network tab, you will notice that dataloader is no longer working as expected. You will see 3 different requests for characters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht2tlcs5if8u2qndyov1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht2tlcs5if8u2qndyov1.png" alt="Image description" width="712" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the root cause
&lt;/h2&gt;

&lt;p&gt;First, lets add a few logs to know when each Card is Suspending the request and when they are resolving it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/Card.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;episode&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;episode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Episode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;before request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCharactersByIds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;episode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getCharacterIdFromUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;after request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then, check the logs...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh2vfnl916qqjwh00qfn3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh2vfnl916qqjwh00qfn3.png" alt="Image description" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, we know that all cards are rendered between .650 and .665 milliseconds. Thats around 15ms to render all cards. If you rerun enough times, you may have different results like something between 10 and 15. If you go back to previous codesandbox (before Suspense), you will also have similar results. So whats happening here? Why adding Suspense breaks it?&lt;/p&gt;

&lt;p&gt;Dataloader docs states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DataLoader will coalesce all individual loads which occur within a single frame of execution (a single tick of the event loop)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And they also provide a function in case you want to change that behavior. So lets use it with its default configuration and add some logs to see whats happening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/api/queries.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;characterDataLoader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;DataLoader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Character&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;characters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCharactersByIds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;uniq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;charactersMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;keyBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;characters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;charactersMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;batchScheduleFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calling callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then check your logs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjjggmiieathtsuzuik75.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjjggmiieathtsuzuik75.png" alt="Image description" width="732" height="780"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, it seems like we found something here: That callback is not supposed to be called multiple times like its happening. If you apply the same changes in the previous codesandbox (before Suspense), the callback will only be called once and after all cards render.&lt;/p&gt;

&lt;p&gt;So lets start changing the behavior of dataloader's &lt;code&gt;batchScheduleFn&lt;/code&gt; by increasing the setTimeout delay to 15ms. &lt;em&gt;Remember how long it takes to render all the Cards?&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;batchScheduleFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;calling callback&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, lets check our logs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3iq5qdry3xwfgwr6fzc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3iq5qdry3xwfgwr6fzc.png" alt="Image description" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wait, this is starting to look like a fix, right? What about our network tab?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fim6txdf4yrh6kk63dnmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fim6txdf4yrh6kk63dnmj.png" alt="Image description" width="592" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, this looks like a fix. But I dont want this number to be this magic. I also would like to know the minimum amount of time I should wait. So lets reduce the 15m and test.&lt;/p&gt;

&lt;p&gt;By bruteforcing this change, you will get to a point where 4ms will sometimes work, sometimes not. And 5ms seems like it will always work?&lt;/p&gt;

&lt;p&gt;But wait, if this magic number is not tied to the amount of time it takes to render my list (15ms), what is it tied to? Why 5ms?&lt;/p&gt;

&lt;h2&gt;
  
  
  Know your internals. Or at least go to conferences?
&lt;/h2&gt;

&lt;p&gt;The moment I started to wonder about those 5ms I immediately remembered about a &lt;a href="https://www.youtube.com/watch?v=pm5rQBL5sk4&amp;amp;ab_channel=ReactConferencesbyGitNation" rel="noopener noreferrer"&gt;talk&lt;/a&gt; I watched on React Summit. One of the statements of the presentation is based in &lt;a href="https://github.com/reactwg/react-18/discussions/27#discussioncomment-800243" rel="noopener noreferrer"&gt;this comment&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Our current heuristic is to yield execution back to the main thread every 5ms. 5ms isn't a magic number, the important part is that it's smaller than a single frame even on 120fps devices, so it won't block animations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So that was it? But why it only happens when you use Suspense? The answer is that when you use Suspense, &lt;a href="https://github.com/reactwg/react-18/discussions/27#discussioncomment-809087" rel="noopener noreferrer"&gt;the tree that got suspended is marked as idle priority&lt;/a&gt;. Meaning that if you dont use it, the rendering of that tree is going to be thread-blocking:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Rendering list &amp;gt; setTimeout &amp;gt; Thread busy &amp;gt; Finished rendering &amp;gt; Thread idle &amp;gt; setTimeout executes &amp;gt; Callback is called.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A different thing happens when youre using Suspense and the tree has idle priority:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Rendering list &amp;gt; setTimeout &amp;gt; Suspense &amp;gt; At 5ms, it rendered the list partially &amp;gt; Yields execution back to the main thread &amp;gt; Main thread is do its thing and executes the setTimeout cause its not blocked by rendering anymore &amp;gt; Main thread becomes idle &amp;gt; Rendering proceeds to next list items &amp;gt; Repeats until the whole list is rendered&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
So basically, the rendering of the list happens in chunks and, because its "non-blocking", every time it yields back to the main thread, the setTimeout executes and triggers dataloader before all the Cards can make their requests.&lt;/p&gt;
&lt;h2&gt;
  
  
  Confirming the assumptions
&lt;/h2&gt;

&lt;p&gt;Surely all of this is only one big assumption from my side. How can I test it?&lt;/p&gt;

&lt;p&gt;Instead of using a setTimeout, we can use the Scheduler to schedule the callback. If this is done, we are not dependant on any "magic numbers". Unfortunatelly, the API is still marked as unstable. But we can already use it for the sake of curiosity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/api/queries.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;unstable_scheduleCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unstable_IdlePriority&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scheduler&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;batchScheduleFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;unstable_scheduleCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unstable_IdlePriority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we have our updated codesandbox with a working solution:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/epic-bohr-jk25cc"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Uncertainty about the 5ms setTimeout
&lt;/h2&gt;

&lt;p&gt;To this date I am not 100% sure if the 5ms timeout works for all scenarios. The Scheduler implementation differs from simply using a setTimeout and there may be some corner cases that the 5ms timeout wont cover.&lt;/p&gt;

&lt;p&gt;I would say that using the Scheduler is a safer approach and recommend it, but unfortunatelly the API is still marked as unstable.&lt;/p&gt;

</description>
      <category>react</category>
      <category>dataloader</category>
      <category>concurrency</category>
      <category>performance</category>
    </item>
    <item>
      <title>Uncovering Frontend Data Aggregation: Our Encounter with BFF, GraphQL, and Hydration</title>
      <dc:creator>Lucas Correia</dc:creator>
      <pubDate>Wed, 22 Mar 2023 12:01:16 +0000</pubDate>
      <link>https://forem.com/tsirlucas/uncovering-frontend-data-aggregation-our-encounter-with-bff-graphql-and-hydration-4l2i</link>
      <guid>https://forem.com/tsirlucas/uncovering-frontend-data-aggregation-our-encounter-with-bff-graphql-and-hydration-4l2i</guid>
      <description>&lt;p&gt;Our team at &lt;a href="https://chilipiper.com" rel="noopener noreferrer"&gt;Chili Piper&lt;/a&gt; has recently started migrating our backend to a microservices architecture. As a front-end developer, I've been documenting my journey and insights while tackling the challenge of handling aggregated data in this new scenario.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: I've made slight modifications to the samples to prevent any potential exposure of Chili Piper internals&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  First of all, what is data aggregation?
&lt;/h2&gt;

&lt;p&gt;Data aggregation in the frontend involves combining and organizing data from multiple sources to display it in a way that makes sense to the user. In a microservices architecture, where services are designed to be agnostic of each other, data aggregation can be a challenge because it may require making multiple requests to different microservices to retrieve all the necessary data.&lt;/p&gt;

&lt;p&gt;This means you will often find yourself loading entities that look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  title: 'Billie Jean'
  artistId: 'some-uuid-here'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To display a card with both the track title and artist name, there are several steps to follow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, make a request to the tracks microservice to load the list of tracks.&lt;/li&gt;
&lt;li&gt;Next, iterate over the tracks to collect the artistId.&lt;/li&gt;
&lt;li&gt;After that, make a batch request to the artists microservice to collect all the artists from the list.&lt;/li&gt;
&lt;li&gt;Finally, map each requested artist back to its corresponding track.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although it seems simple and manageable with just a few lines of JS, it can quickly become complex. This is because the track entity may contain other entities besides the artist, and the artist entity may also contain sub-entities such as bands.&lt;/p&gt;
&lt;h2&gt;
  
  
  The n+1 problem
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The N + 1 problem arises when an application retrieves data from a database and iterates over the results, resulting in repeated database queries. Specifically, the application will issue N additional database calls for every row returned by the initial query, in addition to the original query (+1).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It may seem familiar to what we just discussed, right? If you replace the database with microservices, that's essentially it.&lt;/p&gt;
&lt;h1&gt;
  
  
  The solutions we experimented with
&lt;/h1&gt;

&lt;p&gt;Our use-cases often require us to load a list of items, and then make additional requests to fetch more data to display information related to each individual item. It can quickly become complex if we need to go several levels deep to retrieve all the necessary information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusfg81iy9wtkx8z9jcpl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusfg81iy9wtkx8z9jcpl.png" alt="Image description" width="800" height="675"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Batch endpoints as first solution
&lt;/h2&gt;

&lt;p&gt;Initially, we relied on frontend calls to batch endpoints to display data from multiple sources. However, we soon discovered that this approach had limitations, particularly in terms of performance due to the n+1 problem. Additionally, making multiple requests to the backend in frontend can be costly and not very user-friendly, particularly when waiting for data to load. This led us to explore more efficient data aggregation methods.&lt;/p&gt;

&lt;p&gt;We identified two possible methods for implementing data aggregation in the frontend:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transforming the received data and adding the related entities&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aggregatedCards&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;draftPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publishedPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;updatedDraft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DraftCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payload.cardType&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;cardTypesMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;draftPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardTypeId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;updatedPublished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PublishedCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payload.cardType&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;cardTypesMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;publishedPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardTypeId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updatedDraft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;updatedPublished&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The related entity is directly injected into its parent, making it easily accessible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transformation logic needs to be implemented, adding complexity to the code.&lt;/li&gt;
&lt;li&gt;Predefined types (Our case involves auto-generated types based on our backend's OpenAPI specification) must be extended to accommodate the new entities, which can be cumbersome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dealing with code transformation logics can be even more challenging if the parent entity is part of a union. Here's an example of code we used in one of our experiments:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AggregatedCardA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CardA&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cardType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CardType&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AggregatedCardB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CardB&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cardType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CardType&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AggregatedCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AggregatedCardA&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;AggregatedCardB&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CardData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SimpleMetadata&lt;/span&gt;
  &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AggregatedCard&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CardData&lt;/span&gt;
  &lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CardData&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We had to redefine all the types because of the necessary data transformation to aggregate cardType into CardA and CardB within that union.&lt;/p&gt;
&lt;h2&gt;
  
  
  Relationship maps as alternative
&lt;/h2&gt;

&lt;p&gt;We also explored an alternative approach where instead of transforming the data, we provided a relationship map to send down the React tree. When we needed to use a relationship, we could simply pick it from the map. Here's an example of the code we used for this approach:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aggregateData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCards&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardTypesIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cards&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardTypeId&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardTypes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findBatchedCardTypes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cardTypesIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardTypesMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;keyBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cardTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cardTypesMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cards&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Drawbacks of this approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It can be challenging to memoize the cardTypesMap when passing it down to card items components, depending on the specific use-case.&lt;/li&gt;
&lt;li&gt;The approach relies on prop drilling the cardTypesMap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Note: We considered implementing these features on a raw node BFF server to reduce loading times and eliminate the need for multiple backend calls. However, we realized that we would still face the same issues with types and data transformation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Following discussions with individuals outside our organization, we decided to experiment with GraphQL as a potential solution.&lt;/p&gt;
&lt;h2&gt;
  
  
  The findings when experimenting with GraphQL
&lt;/h2&gt;

&lt;p&gt;In short, we chose not to pursue GraphQL due to some limitations with union types and a lack of support for maps. This is further detailed in this link: &lt;a href="https://github.com/graphql/graphql-js/issues/53" rel="noopener noreferrer"&gt;limitations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We are aware that we could use aliases as a workaround, but that would mean the data structure would differ from what our types are expecting, and we would face the same issue of having to extend our types.&lt;/p&gt;

&lt;p&gt;Anyway, we came across two amazing tools that are useful in situations where a BFF is necessary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Urigo/graphql-mesh" rel="noopener noreferrer"&gt;graphql-mesh&lt;/a&gt; is a tool that allows you to integrate backend microservices (whether they are REST with OpenAPI specs, GraphQL, etc.) into a single GraphQL Gateway. It's easy to set up as it generates schemas, queries, and mutations based on the provided specifications. You only need to implement additional properties for data aggregation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main setup for us consisted of three files:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;.meshrc.yml&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Card&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://cp.com/card&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;card.json&lt;/span&gt;
        &lt;span class="na"&gt;operationHeaders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{context.headers['authorization']}"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CardTypes&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://cp.com/card-type&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;card-type.json&lt;/span&gt;
        &lt;span class="na"&gt;operationHeaders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{context.headers['authorization']}"&lt;/span&gt;

&lt;span class="na"&gt;additionalTypeDefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./typeDefs/card.graphql&lt;/span&gt;
&lt;span class="na"&gt;additionalResolvers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./resolvers/card&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;./typeDefs/card.graphql&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;extend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;cardType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CardType&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;./resolvers/card&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;print&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SelectionSetNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;keyBy&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lodash/keyBy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Maybe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Resolvers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../.mesh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Resolvers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cardType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;selectionSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
      {
        cardTypeId
      }
      `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CardTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_card_types&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardTypeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;argsFromKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;valuesFromResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Maybe&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Maybe&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;keyBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cardTypeId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="nf"&gt;selectionSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;selectionSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SelectionSetNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectionSet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Another tool we discovered was &lt;a href="https://wundergraph.com" rel="noopener noreferrer"&gt;wundergraph&lt;/a&gt;, which we didn't have the chance to fully explore. However, based on our initial research, it appears to be similar to graphql-mesh but more powerful, using JSON-RPC instead of GraphQL.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Mixed approach: Lazy-loading with dataloader
&lt;/h1&gt;

&lt;p&gt;What if, instead of loading all the necessary data at the list-level, we loaded it at the item-level as needed? This would eliminate the need for data transformation or changing types, and there would be no prop drilling. Instead, we would load the data for each individual item when it is needed and use it directly where it is needed. Additionally, we could display a placeholder or blurred version of the component while the data is loading:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffdoa3rxksia2sax264n8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffdoa3rxksia2sax264n8.png" alt="Image description" width="800" height="224"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cardType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCardType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardTypeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cardTypeId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// if its loading, render blur version, if its not, render cardType info&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The downside of the item-level loading approach is that it requires each item to make its own request, which can lead to a high volume of requests to the backend as the number of items increases. However, after researching potential solutions, we found that GraphQL's dataloader tool could help us address this issue through its batching feature.&lt;/p&gt;

&lt;p&gt;For those who are unfamiliar with the dataloader batching feature, it groups together requests that occur in the same event loop tick and calls the provided loader to make a single request instead of multiple. Once it receives the results, it automatically maps the response back to each individual call. This feature essentially solves the main issue with the lazy-loading strategy.&lt;/p&gt;

&lt;p&gt;Implementing a react-query + dataloader hook is a simple process. Here's how we did it:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cardTypesLoader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Dataloader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resultMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;CardTypesApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findCardTypes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;resultMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- IMPORTANT, dataloader doesn't have the same cache management as react-query&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useCardType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;card-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cardTypesLoader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;options&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  How does it solve the problem of data aggregation?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;First, let's address the question: Why is data aggregation necessary?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We want to have a fast way to request all the sub-entities so users can see/interact with our items as soon as possible. We want to reduce user idle time.&lt;/li&gt;
&lt;li&gt;We want to map all the requested sub-entities to their respective parent. So in our case, we want to aggregate the card type into the card because we want to use it in the card component.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How does lazy loading with data-loader address these issues?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With lazy-loading, we can display list items to the user without waiting for the additional information related to other microservices to load. This approach allows users to see and interact with cards sooner than any other approach, including BFF, and results in the least amount of idle time.&lt;/li&gt;
&lt;li&gt;By using dataloader, we can use react-query in each item of our list to request the sub-entity we want to use in that item. This eliminates the need to map sub-entities to entities since the place we request them is the place where we use them. We don't need to change types or data structures in any way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Extra points for lazy-loading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Since we will be utilizing batch endpoints and not aggregating data, the final JSON payload exchanged between the backend and frontend is reduced. This is because duplicates can be removed when sending the request to the dataloader if two cards have the same card type.&lt;/li&gt;
&lt;li&gt;By using react-query to retrieve sub-entities in a pagination scenario, already-resolved entities in page 1 will not be re-requested in page 2.&lt;/li&gt;
&lt;li&gt;This is also true when using a virtualization library. Sub-entities are only loaded for what's on the user's screen, and when the user scrolls down, react-query cache is utilized to prevent reloading what's already been requested.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;But hey, wont we have too many rerenders?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using dataloader actually groups all sub-entity requests for a given entity, resulting in simultaneous resolution of requests and minimizing rerenders, even in scenarios with a high number of items on a page. For example, in the team card card, 3 sub-entity requests are required (card-type, user, and progress), which means that regardless of the number of cards on the screen, only 3 (visible) rerenders will occur.&lt;/p&gt;

&lt;p&gt;Additionally, incorporating smooth CSS transitions between the loading state and actual state can enhance the user experience even further.&lt;/p&gt;

&lt;p&gt;Note that if the 3 rerenders in your list are causing performance issues, it may be more effective to address these issues through virtualization rather than attempting to reduce the number of rerenders.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hydration as a opt-in performance improvement
&lt;/h2&gt;

&lt;p&gt;This approach allows you to take advantage of react-query &lt;a href="https://tanstack.com/query/v4/docs/react/guides/ssr#using-hydration" rel="noopener noreferrer"&gt;hydration&lt;/a&gt; for data prefetching, even without utilizing SSR. By creating a node endpoint that fetches your queries and returns a dehydrated state to the frontend, you can benefit from partial hydration. This enables you to prefetch essential queries while leaving the frontend to lazy-load the rest. Consequently, you achieve performance comparable to the BFF approach, without the need to manage aggregation logic.&lt;/p&gt;

&lt;p&gt;It also has a built-in fail-safe: if the prefetch endpoint is down, the frontend will resort to directly requesting data. This ensures that your application remains functional even in the face of potential issues.&lt;/p&gt;

&lt;p&gt;A downside is the necessity of introducing prefetching logic to the BFF, though it's notably less code than aggregation.&lt;/p&gt;
&lt;h2&gt;
  
  
  There is no silver bullet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In instances where the n+1 problem occurs or when optimizing performance is essential, implementing the prefetching strategy is not optional.&lt;/li&gt;
&lt;li&gt;If you find yourself in a scenario where you need to handle data from several interdependent sources and your processing logic requires data from both sources prior to displaying it to the user, then this alternative is likely unsuitable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  In the end, which approach did we take at Chili Piper?
&lt;/h2&gt;

&lt;p&gt;General consensus claims that the lazy-loading and dataloader method is a more elegant solution and it is commonly adopted in various scenarios. In a particular case, we use BFF due to the need to process the data before presenting it to the user.&lt;/p&gt;

&lt;p&gt;As a bonus, here's how lazy-loading looks like in one of our pages:&lt;/p&gt;


&lt;div&gt;
  &lt;iframe src="https://loom.com/embed/589c053aa0904ca49286b867d87bbf6d"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;



</description>
      <category>microservices</category>
      <category>graphql</category>
      <category>bff</category>
      <category>reactquery</category>
    </item>
  </channel>
</rss>
