DEV Community

Hasura
Hasura

Posted on • Originally published at hasura.io on

Scaling frontend app teams using Relay

Scaling frontend app teams using Relay
The UI is decomposed into multiple React components.

Scaling frontend app teams using Relay

The basic idea behind scaling the frontend, much like scaling any other part of the stack, is factoring it into multiple components:

  • Can be owned by multiple independent specialised teams
  • Reduce coupling between components
  • Have clearly specified interfaces between components

Let's look at how this strategy plays out, and how to use modern technologies and ideas like Relay and backend-for-frontend (BFF) to scale out frontend applications and teams.

Independent isolated teams own different components

This is a useful baseline from which to iterate. It represents the naive scenario in which teams independently develop different components in a larger app but in isolation from each other.

Scaling frontend app teams using Relay
Independently developed components with independent data fetching. The circles are different teams.

Problem: Independent data fetching

  • Poor performance and UX jank
  • A component may make multiple network requests
  • Component data request may depend on ancestor data, leading to waterfall style requests
  • Components may fetch redundant data

Problem: Independent state management

  • Data and UI inconsistency

Batching of network requests

Teams can manually coordinate data fetching:

  • Batch all data requests at the root level
  • Distribute data through the component tree via props

Scaling frontend app teams using Relay
Teams manually batch data requests through a shared root level query.

Problem: Coupling at root query

  • Adding a query: can duplicate other similar queries, and fetch redundant data
  • Removing or editing a query:
  • Can break another component that (implicitly) depends on the same data
  • Not removing unused data leads to over-fetch and cruft

Problem: Poor developer ergonomics

  • The data requirements for a component are no longer colocated with the component itself, which breaks encapsulation

TRPC / React Query is not a complete solution

  • Batches parallel network queries into single network requests
  • Cannot batch all queries needed for a page
  • Cannot solve waterfall requests
  • Queries are batched over the network layer, but still execute against the data layer as independent queries i.e. batching cannot leverage the internal structure or relations of queries

Coordinate data access through a centralized cache store

Scaling frontend app teams using Relay
Teams manually coordinate shared state through a central store.

  • Introduces coupling between teams, with similar issues as with batching queries
  • Lots of boilerplate to normalize data, update stores, and plumb data to components

Backend for frontend

BFF is useful for making lighter and more performant client applications by moving compute and data transformations to the server.

Scaling frontend app teams using Relay
Backend for frontend: Moves compute and data transformations to the server. Collectively owned by all frontend teams.

  • Owned by the client app team
  • Doesn't solve any of the coupling problems, just moves it around

GraphQL

Batched queries organized around client pages instead of server functionality couple the frontend and the backend teams.

Scaling frontend app teams using Relay
Backend developers build APIs organized around Frontend pages/routes.

Instead, a GraphQL API exposes all features of the backend at a single endpoint.

  • The client app can craft queries that fetch exactly the data needed in one shot

Scaling frontend app teams using Relay
Backend GraphQL API organized around server capabilities allows for flexible frontend queries.

Even better, we begin to see that the query structure beings to mirror the component tree!

  • This means we can refactor a root level query into fragments

Scaling frontend app teams using Relay
Queries decompose into fragments.

Putting it all together with Relay

In Relay, every component defines its own data requirements

Scaling frontend app teams using Relay
Data dependencies colocated with components. Fragment structure mirrors the component tree.

  • Significantly, a component can only access the data it has explicitly requested. This is "data masking" and is enforced through the useFragment hook.
  • This means that teams can modify individual components with confidence knowing that nothing will break as there are no implicit data dependencies

Scaling frontend app teams using Relay
Independently declared data dependencies are compiled into a single root level query by the Relay compiler.

At build time, the Relay compiler builds an optimized set of top level queries from all the fragments

  • The compiler can check for common errors, and run optimizations such as deduplication across the whole codebase
  • You get the developer ergonomics of independently developed components, but with the efficiency of globally optimized and batched data fetching

Scaling frontend app teams using Relay
Relay can leverage the rich information present in the GraphQL schema. Incrementally adopt the global node id and connection spec.

Relay uses the rich type information in the GraphQL schema, and automatically builds a local cache of data from all queries.

Further, by incrementally adopting features such as the node global id spec and the connection spec, you get advanced features such as:

  • Reloading only a portion of a query via fragments
  • Cursor based pagination

Conclusion

The JavaScript ecosystem has produced a variety of solutions for frontend development, data fetching, and state management; but solutions often fall short for ambitious projects.

GraphQL, Relay, and React were built to work together and have the huge benefit of being driven by Meta's (Facebook) experience building and maintaining extremely large and and complex applications developed by many teams.

if you’re not composing GraphQL fragments from multiple components into one query (as Relay does), i think you’re missing 80% of the point of GraphQL.

which is ok but isn’t talked about enough https://t.co/ooohCDV6ZO

— danabramov.bsky.social (@dan_abramov) March 12, 2023

It seems that GraphQL and React has taken over the world, but people are often put off by the new conventions espoused by the Relay library.

The good news is that Relay can be incrementally adopted.

  • The client library will work with any existing GraphQL API
  • Adopting bits of the Relay spec unlocks additional features
  • Relay can be adopted selectively by some components and not others, for an easy migration path

Even better, it seems that the people who've implemented Relay, have been happy with it for quite a while.

How Coinbase is scaling their app with Relay

"Relay is unique among GraphQL client libraries in how it allows an application to scale to more contributors while remaining malleable and performant."

https://t.co/D45yEid8l0

— Relay (@RelayFramework) May 6, 2022

Join us for a live Q&A session with Tanmai Gopal on the @GraphQL Discord. 🎙️ Discover how to scale UI development with Relay #GraphQL.

⏰ July 12, 11AM PT

Join GraphQl Discord server ➡️ https://t.co/MM3HN7DpbW pic.twitter.com/hhKYYcnbKc

— Hasura (@HasuraHQ) July 7, 2023

It still surprises me how Twitter embraces GraphQL / Relay (for RWeb) but keeping their Timeline model instead of adapting the Relay connection spec https://t.co/skOcPvrmMv

— Jane Manchun Wong (@wongmjane) November 19, 2022

💡 #RelayJs feature I appreciate - Relay is optimized for performance by default. It “forces” you to break down the data requirements of your UI in small, reusable parts, like React does with components. It'll then subscribe to changes of only the data each component asks for 1/x

— Gabriel Nordeborn (@___zth___) April 3, 2022

Use Hasura to easily generate a Relay compatible API with no code, across multiple data stores, with cross data store joins, filtering, and aggregations, and declaratively defined permissions.

Sign up now for Hasura Cloud to get started!

Top comments (0)