Dec 7, 2023

Announcing Fuse.js: The opinionated framework for creating typesafe data layers

We are excited to announce Fuse.js, an opinionated open source framework for creating typesafe data layers with ease. It’s tailor-made to make it simple to transform backend APIs to frontend needs.

We’ve spoken with hundreds of companies about their APIs and how they manage the interface between backend and frontend, and we’ve learned that a select group of the most successful companies all use data layers.

But this data layer architecture isn’t widely known and hasn’t had a name—until today.

Let's dive in!

Introducing data layers: Ship faster at scale

A data layer is a dedicated service between the backend APIs and the clients. Crucially, it is owned by the frontend teams, enabling them to connect, transform, and extend backend APIs for their UIs.

Data layers allow engineering teams to move at optimal speed. They achieve this by solving three core problems at the interface between backend and frontend:

Problem 1: Backend engineers think about resources, but frontend teams think about user interfaces

Whether it's databases or microservices, resources are at the core of a backend engineer’s work. Frontend engineers, however, focus on bringing user stories and experiences to life.

Most companies today bridge this difference in thinking by having backend and frontend engineers collaborate to build APIs that match the frontend engineers’ needs.

But, that is often a slow and painful process because it requires governance, review, and consensus, all the while asking backend engineers to think more like frontend engineers and for frontend engineers to think more like backend engineers.

Data layers enable each team to work however they prefer with minimal friction. Backend teams can continue exposing the resource-based APIs they know and love, and frontend teams can transform the backend APIs to match their needs on their own.

Problem 2: UI development gets blocked by API development

Different teams have different priorities, and development schedules are often misaligned. When the timing of deliverables is out of sync, the frontend teams are blocked until the backend teams finish implementing the backend APIs.

Data layers allow engineering teams to parallelize their work. Frontend teams can develop the UI even if the backend API isn’t ready because the UI can be implemented on top of the data layer independently of having actual data from the backend API. This means everything is ready to ship once the backend APIs are ready and plugged into the data layer.

Problem 3: Resource-based APIs are one-size-fits-all, but different UIs need different data

UIs need to adapt to their specific environment. Each device and form factor has unique needs, but resource-based APIs are a one-size-fits-all solution that can’t adapt to such variability. Therefore, they have to cover the union of all the clients, rendering the APIs bloated for most client needs, often directly impacting performance.

Thanks to GraphQL, data layers expose flexible APIs that can cover different needs for different UIs. GraphQL APIs enable this by defining what data is available, which the clients can query for only the exact data they need.

Data layers compared to Backend-for-Frontends (BFFs)

The three problems aren’t new to software development; companies have tried solving them for years. One of the most widely adopted solutions today is using backend-for-frontends (BFFs), where each frontend has its own backend service that transforms the underlying backend APIs tailored to the bespoke requirements of that frontend.

While BFFs solve the aforementioned three problems, as Lee Bryon pointed out more than 8 years ago, having one BFF per UI introduces its own set of new problems:

  • Duplicating significant amounts of work because, while frontends differ in their needs, there are still many similarities between their transformations

  • Creating product inconsistencies across different frontends because different BFFs will fix the same bugs and implement the same features at different times

  • Making it difficult to create new frontends as that requires bootstrapping another BFF

  • Requiring careful coordination of deployments between the client and the BFF as they are tightly coupled

Having one central data layer (instead of one per frontend) circumvents these problems, but the concern with that centralization was that it would “become bloated by handling multiple concerns.” (source)

That is where GraphQL comes into the picture.

GraphQL APIs enable clients to query for the exact data their UI needs. That means “becoming bloated by handling multiple concerns” is not an issue because GraphQL APIs don’t contain any specific frontend’s concerns.

Building a central data layer based on GraphQL is the best of all worlds: it solves the three problems at the interface between backend and frontend without the added challenges introduced by using BFFs.

Data layers compared to GraphQL Federation

Another solution commonly adopted to address the core problems at the interface between backend and frontend is GraphQL Federation. Federation composes a central GraphQL API (”supergraph”) from many underlying microservices that each expose a small part of the GraphQL schema (”subgraphs”).

Federation addresses the second and third problems at the interface between backend and frontend teams: unblocking UI development from API development and different UIs needing different data.

However, it significantly exacerbates the first problem: the differences in how backend and frontend teams think.

Federation requires that every microservice expose a schema that matches not only one UI’s needs but all the UIs’ needs, as it gets accessed by all the clients directly, with no ability for frontend engineers to transform it.

This requires significantly more communication between frontend and backend teams as backend engineers are forced to learn about requirements across all the UIs. They must also learn an additional technology they otherwise do not utilize in their day-to-day work for their frontend teams.

In comparison, data layers solve all three problems at the interface between backend and frontend teams while allowing backend teams to continue working the way they know and love without adaptations.

Introducing Fuse.js: The opinionated framework for creating typesafe data layers

Because data layers aren’t widely known, there are no tools specifically optimized for creating them. Every company that wants to build one today has to start from scratch.

That’s why we made Fuse.js, an opinionated open source framework for creating typesafe data layers with ease.

We took our favorite packages of the GraphQL ecosystem (Pothos, GraphQL Yoga, and urql), integrated them well out of the box, and created an opinionated abstraction of nodes on top that is tailor-made to make it simple to transform backend APIs to frontend needs:

We architected Fuse.js to guide users down a pit of success with optimal data fetching where the opinionated "right way" is the easy way:

  • Data requirements are defined per component, allowing codebases to scale no matter the size.

  • Every page gets exactly, and only, the data it needs in a single network request.

  • Nodes are automatically data loaded under the hood—no more n+1 problems.

We also understand the importance of excellent developer experience. So, we've integrated everything you’ve come to expect from today’s best tools:

  • Typesafe end-to-end from data source to client component

  • Autogenerated entity queries for nodes

  • Simple APIs for common tasks like lists with pagination

  • IDE autocomplete across server and client

  • Hot module-replacement for any changes

  • API playground

First-class support for Next.js

Fuse.js is technically agnostic to the clients you have because it creates a GraphQL API that can be queried from anywhere. However, in this initial release, we are shipping with first-class support for hosting your Fuse.js data layer in Next.js.

You can run a single command (npx create-fuse-app), and we will bootstrap your Fuse.js data layer in your Next.js app.

We also built and integrated a specific optimization for React Server Components into Fuse.js—you can execute GraphQL queries in the same process as the server component renderer to avoid the overhead of another network request to an API:

import { graphql } from '@/fuse'
import { execute } from '@/fuse/server'
// UserProfile can be a server or a client component
import UserProfile from '@/components/UserProfile'
const UserQuery = graphql(`
query User($id: ID!) {
user(id: $id) {
...UserProfile_UserInfo
}
}
`)
export default async function UserProfilePage({ params }) {
const result = await execute({
query: UserQuery,
variables: { id: params.id }
});
}

If you have client components and are using the pages router or have more clients, you can define the data requirements exactly the same way but instead, fetch them from the GraphQL API with a network request:

'use client';
import { graphql } from '@/fuse'
import { useQuery } from '@/fuse/client'
import UserProfile from '@/components/UserProfile'
// This is the exact same as in the server component!
const UserQuery = graphql(`
query User($id: ID!) {
user(id: $id) {
...UserProfile_UserInfo
}
}
`)
export default async function UserProfilePage({ params }) {
const [result] = useQuery({
query: UserQuery,
variables: { id: params.id }
});
}

All of this is fully typesafe, all the way from the data layer to the React component. 🤯

Known limitations and what’s next

  • Some day soon, our docs will be leveled up to where you don’t need to know GraphQL; until then, you’ll need basic familiarity with writing GraphQL queries.

  • At launch, Fuse.js supports Next.js out of the box. All other paths are unpaved but technically feasible. We plan on expanding support based on demand.

  • The React Server Components support is close to, but not quite optimal, because Next.js currently doesn't have an API for batching data requirements for multiple server components rendered on the same page. So, in this first release, you might execute multiple queries on a page even though they could theoretically be batched together into one execution.

    • However, we are already in communication with the Next.js team and will integrate any API they add into Fuse.js as soon as it becomes available.

Data layers: Everybody works the way they prefer with minimal friction

We’re excited to share the data layer architecture with you.

Fundamentally, the data layer is an architectural approach that brings a cultural change in the engineering organization by shifting the ownership and work to those with the most utility and benefit.

Our bet is that adopting the data layer will have a material impact on any company's ability to ship faster because it fundamentally aligns with how both backend and frontend engineering teams naturally want to work.

We built and open sourced Fuse.js to lessen your load in giving this new architecture a try. As an engineering community, this is an ever-evolving conversation and we have no doubt that we will all keep iterating together. Let’s continue the collaboration & conversation!