命运:一个用于 React 和 tRPC 的现代数据客户端
Fate: A modern data client for React and tRPC

原始链接: https://fate.technology/posts/introducing-fate

## Fate:适用于 React 和 tRPC 的现代数据客户端 Christoph Nakazawa 宣布发布 **fate** 的初始 alpha 版本,这是一款专为 React 和 tRPC 设计的新数据客户端,旨在简化数据获取和状态管理。Fate 借鉴了 GraphQL/Relay 的优势——规范化缓存、片段组合和声明式数据需求——将这些优势带到 tRPC 生态系统中。 Fate 优先考虑最小化的、仅使用 JavaScript 的 API,避免使用 DSL 或“魔法”。它专注于组件*需要*什么数据,而不是*如何*获取数据,在根级别组合数据请求以提高效率。数据按对象缓存,而不是按请求缓存,从而减少样板代码并提高一致性。 主要功能包括定义数据依赖关系的声明式“Views”,通过支持乐观更新和自动回滚的“Actions”与 tRPC 突变无缝集成,以及与现代 Async React 功能(如 Suspense 和 React Compiler)的完全兼容性。 Fate 旨在解决典型 React 数据获取模式的复杂性,提供更清晰、更易于维护的方法。虽然仍处于早期开发阶段,但它提供了一个现成的模板,方便快速设置,并欢迎社区贡献。代码的很大一部分甚至是在 OpenAI 的 Codex 的帮助下生成的。

## Fate:一个新的 React 数据客户端 一个名为“Fate”的新数据客户端(fate.technology)已发布,适用于 React 和 tRPC,由一位在 Jest、Yarn 和 Metro 等流行 JavaScript 工具方面拥有丰富经验的开发者创建。 项目名称是对 Facebook 历史的致敬——参考了“f8”开发者大会,并微妙地呼应了“face”。Fate 旨在高效地满足数据请求。 Hacker News 上的早期反馈积极,用户们对尝试它感到兴奋,特别是那些熟悉 Relay 的用户。一位用户表达了对未来支持 oRPC 的希望,作者对此表示欢迎贡献并添加该功能。该项目似乎建立在 GraphQL 生态系统中具有弹性和前瞻性的理念之上,并将其扩展到该领域之外。
相关文章

原文

December 9th 2025 by Christoph Nakazawa

I'm excited to announce the initial alpha release of fate, a modern data client for React & tRPC. fate combines view composition, normalized caching, data masking, Async React features, and tRPC's type safety.

A modern data client for React & tRPC

fate is designed to make data fetching and state management in React applications more composable, declarative, and predictable. The framework has a minimal API, no DSL, and no magic—it's just JavaScript.

GraphQL and Relay introduced several novel ideas: fragments co‑located with components, a normalized cache keyed by global identifiers, and a compiler that hoists fragments into a single network request. These innovations made it possible to build large applications where data requirements are modular and self‑contained.

Nakazawa Tech builds apps and games primarily with GraphQL and Relay. We advocate for these technologies in talks and provide templates (server, client) to help developers get started quickly.

However, GraphQL comes with its own type system and query language. If you are already using tRPC or another type‑safe RPC framework, it's a significant investment to adopt and implement GraphQL on the backend. This investment often prevents teams from adopting Relay on the frontend.

Many React data frameworks lack Relay's ergonomics, especially fragment composition, co-located data requirements, predictable caching, and deep integration with modern React features. Optimistic updates usually require manually managing keys and imperative data updates, which is error-prone and tedious.

fate takes the great ideas from Relay and puts them on top of tRPC. You get the best of both worlds: type safety between the client and server, and GraphQL-like ergonomics for data fetching. Using fate usually looks like this:

Learn more about fate's core concepts or get started with a ready-made template.

Journey to fate

I was part of the original Relay and React teams at Facebook in 2013, but I didn't build Relay. While I worked on deploying the first server-side rendering engine for React and migrating Relay from React mixins to higher-order components through codemods, I honestly didn't fully grasp how far ahead everyone else on the Relay team was back then.

In the following years, Relay became the default data framework at Facebook. It was such an elegant way to handle client-side data that I had assumed it would gain widespread adoption. That didn't happen, and its backend companion GraphQL has become divisive in the web ecosystem.

I spent several years rebuilding a stack similar to the frontend stack at Facebook, using GraphQL and Relay, and even forking and rewriting abandoned Facebook libraries such as fbtee. I built Athena Crisis, a video game, and many other apps using this stack.

However, in recent months I have been exposed to the reality of React data fetching. It tends to look something like this:

This boilerplate is repetitive and ok, but not great. The real problems start when data changes. Mutations tend to have complex logic with detailed patches to the local cache or for handling rollbacks. For example:

When your data client is an abstraction over fetch, keeping client state consistent gets hard quickly. Correctly handling mutations often requires knowing every place in your application that might fetch the same data. That often leads to defensive refetching and waterfalls down the component tree. Component trees frequently look like this:

Tree

To be clear: These libraries are great at fetching data. I know better patterns are available in most of these libraries, and advanced developers can avoid many of the downsides. Sync engines address these problems, but they're challenging to adopt and also come with trade-offs.

Still, it's too easy to get something wrong. Codebases become brittle and hard to maintain. Looking ahead to a world where AI increasingly writes more of our code and gravitates towards simple, idiomatic APIs, the problem is that request-centric fetch APIs exist at all.

Building fate

I did not want to compromise on the key insights from Relay: a normalized cache, declarative data dependencies, and view co-location. At around the same time, I watched Ricky Hanlon's two-part React Conf talk about Async React and got excited to start building.

When fetch-based APIs cache data based on requests, people think about when to fetch data, and requests happen at every level of the component tree. This leads to boilerplate, complexity, and inconsistency. Instead, fate caches data by objects, shifts thinking to what data is required, and composes data requirements up to a single request at the root.

A typical component tree in a React application using fate might look like this:

Tree

Using fate

fate's API is minimal: It's just JavaScript, focused on answering: "Can we make development easier?"

Views

Let me show you a basic fate code example that declares its data requirements as a "view", co-located with a component. fate requires you to explicitly "select" each field that you plan to use in your components as a "view" into your data:

A ViewRef is a reference to a concrete object of a specific type, for example a Post with id 7. It contains the unique ID of the object, the type name and some fate-specific metadata.

fate creates and manages these references for you, and you can pass them around your components as needed to resolve them against their views.

Requests

Pass the composed views to useRequest at the root of your app, and it'll suspend and fetch data in a single request using tRPC's HTTP Batch Link.

Actions

fate does not provide hooks for mutations like traditional data fetching libraries do. Instead, all tRPC mutations are exposed as actions for use with useActionState and React Actions. They support optimistic updates out of the box.

A LikeButton component using fate Actions and an async component library might look like this:

When this action is called, fate automatically updates all views that depend on the likes field of the particular Post object. It doesn't re-render components that didn't select that field. There's no need to manually patch or invalidate cache entries. If the action fails, fate rolls back the optimistic update automatically and re-renders all affected components.

All of the above works because fate has a normalized data cache under the hood, with objects stored by their ID and type name (__typename, e.g. Post or User), and a tRPC backend conforming to fate's requirements, exposing byId and list queries for each data type.

You can adopt fate incrementally in an existing tRPC codebase without changing your existing schema by adding these queries alongside your existing procedures.

Clarity

With these three code examples we covered almost the entire client API surface of fate. As a result, the mental model of using fate is dramatically simpler compared to the status quo. fate's API is a joy to use and requires less code, boilerplate, and manual state management.

It's this clarity together with reducing the API surface that helps humans and AI write better code.

Async

Finally, by using modern Async React, the latest React DevTools features for Suspense and Component Tracks, the React Compiler and even Hot Module Reloading (HMR) for data views work out of the box.

Get Started

Get started with a ready-made template quickly

fate-template comes with a simple tRPC backend and a React frontend using fate. It features modern tools to deliver an incredibly fast development experience. Follow its README.md to get started.

Read about the Core Concepts, or jump right in and learn about Views.

You can also try a runnable demo directly in your browser:

Open in GitHub Codespaces

Future

fate is not complete yet. The library lacks core features such as garbage collection, a compiler to extract view definitions statically ahead of time, and there is too much backend boilerplate. The current implementation of fate is not tied to tRPC or Prisma, those are just the ones we are starting with. We welcome contributions and ideas to improve fate. Here are some features we'd like to add:

  • Support for Drizzle
  • Support backends other than tRPC
  • Persistent storage for offline support
  • Implement garbage collection for the cache
  • Better code generation and less type repetition
  • Support for live views and real-time updates via useLiveView and SSE

NOTE

80% of fate's code was written by OpenAI's Codex – four versions per task, carefully curated by a human. The remaining 20% was written by @cnakazawa. You get to decide which parts are the good ones! The docs were 100% written by a human.

If you contribute to fate, we require you to disclose your use of AI tools.

Please try out fate and share your feedback. I'm excited to hear what you think and how it works for you.

If fate does not live up to its promise, I hope that Relay's and fate's ideas will impact future data libraries for the better.

Thank you for reading.

联系我们 contact @ memedata.com