Isograph is a feature-incomplete proof-of-concept framework for building React apps that fetch GraphQL data.
See this demo and live coding session.
This post is a snapshot of my latest thinking about the framework. Everything is likely to change.
Isograph allows you to write web apps pretty much entirely using functions from graph data to an arbitrary value, known as resolvers. Importantly, React components can be used as resolvers. Since these resolvers' graph data can include other resolvers, entire apps can be built with resolvers.
In this post, I lay out the intended developer experience of Isograph and discuss the advantages of structuring your app as a tree of resolvers. Then, I talk about how the features that Isograph will gain over time will allow developers to write more powerful, more correct and more performant apps.
The developer experience of Isograph resolvers
You can also get a sense of the developer experience (as of June, 2023) of Isograph by watching this video, in which I add a simple pull request screen to the github demo.
The low-level building block in Isograph is a resolver, which is a function from some graph data to some value. Consider this example:
The Isograph compiler will find this iso
literal and modify the GraphQL schema to add a full_name
field to the User
type. Other resolvers can then select this resolver field as if it was a regular server field.
Resolvers can also be used to expose components through the graph. Consider:
Here, we see a regular React component, UserDetailComponent
, that has an additional data
field passed in.
This is a fully-powered component. Suspense, context, and hooks all work.
An easier developer experience:
The use of resolvers can make development easier in several ways:
The parameters are statically typed, and the type will be inferred from the
iso
literal.An Isograph VSCode extension, powered by a language server, will be able to validate the
iso
literal in real time, and provide suggestions, meaning that it's easy to only select valid fields.This extension will be able to surface all resolvers and server fields. This discoverability will help those who are new to a codebase to quickly become productive.
Since these resolvers are additional fields in the GraphQL schema, existing GraphQL tooling should also be able to show these resolvers as fields.
There is no need to import another Isograph component. Instead, just select it.
The features that Isograph resolvers will (eventually) have
Over time, what Isograph does with the GraphQL schema will become more ambitious. Here is a sampling:
Mutations and subscriptions can be exposed from queries. For example, a user might have a
change_name
method selectable from the graph, which is mapped to a mutationMutation.change_name
.In particular, mutations and subscriptions will have the option of selecting all the server fields that are selected on that mutation's parent type (e.g. the user) in the generated query. So, if you have a
update_user
mutation, it can be sure to fetch all of the necessary fields and you can completely avoid underfetching and showing missing or stale data.
Resolvers that are deferred can have their data and Javascript deferred. Resolvers should also be able to be imperatively loaded. (Effectively, this is entrypoints.)
Components (and other resolvers) that are exposed through the graph can include injected code (e.g. analytics), giving you a precise understanding of what data was necessary to fetch. Isograph is uniquely position to provide actionable insights like: "this footer component is never onscreen for 99% of users — how about deferring its data, or loading it imperatively when its component is 600px from being onscreen?"
Isograph is well-suited for apps that prioritize correctness
There are two features that, combined, will make Isograph a compelling choice for apps that need precise control over what data they show users. For example, an app that can never show stale or missing data (such as a banking or trading app) may be well-served by Isograph.
It will be possible to be extremely precise about the type of a field. For example, a server field may have type
MaybeLoaded<T>
. It can be handled as-is by the resolver (i.e. it would receive a discriminated union). Or, it can be unwrapped, which would cause the parent field to gain aMaybeLoaded<...>
type. (MaybeFieldLevelError
,MaybeNull
andMaybeStale
are other such wrappers.)It will also be possible to opt into behavior, at the query level, that if a query can reach missing data, it will automatically refetch.
Thus, Isograph can be configured to never show stale or missing data. If any data becomes missing or stale, suspend pending a refetch.
Current state of Isograph
There's a working demo and a video of me adding a screen. Most features are missing, and there are minor known bugs. But the demo is sufficient to show off the developer experience.