{"id":13838451,"url":"https://github.com/isographlabs/isograph","last_synced_at":"2026-04-08T19:31:00.001Z","repository":{"id":175202279,"uuid":"622052729","full_name":"isographlabs/isograph","owner":"isographlabs","description":"The UI framework for teams that move fast — without breaking things.","archived":false,"fork":false,"pushed_at":"2026-02-26T23:15:02.000Z","size":18896,"stargazers_count":382,"open_issues_count":373,"forks_count":40,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-04-05T17:35:00.556Z","etag":null,"topics":["app-development","data-fetching","graphql","react","web-development"],"latest_commit_sha":null,"homepage":"https://isograph.dev/docs","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/isographlabs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-MIT","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-04-01T01:45:06.000Z","updated_at":"2026-04-02T08:40:28.000Z","dependencies_parsed_at":null,"dependency_job_id":"baa04d76-f2d4-4753-ada8-1ebcaf7147c8","html_url":"https://github.com/isographlabs/isograph","commit_stats":null,"previous_names":["isographlabs/isograph"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/isographlabs/isograph","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isographlabs%2Fisograph","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isographlabs%2Fisograph/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isographlabs%2Fisograph/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isographlabs%2Fisograph/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/isographlabs","download_url":"https://codeload.github.com/isographlabs/isograph/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isographlabs%2Fisograph/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31571599,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["app-development","data-fetching","graphql","react","web-development"],"created_at":"2024-08-04T15:01:57.780Z","updated_at":"2026-04-08T19:30:59.983Z","avatar_url":"https://github.com/isographlabs.png","language":"Rust","readme":"# Isograph\n\nThe framework for teams that move fast — without breaking things.\n\nIsograph makes it easy to build robust, performant, data-driven apps.\n\n- Read the [docs](https://isograph.dev/docs/), especially the [quickstart guide](https://isograph.dev/docs/quickstart/).\n- Watch the [talk at GraphQL Conf 2024](https://www.youtube.com/watch?v=sf8ac2NtwPY) (and from [2023](https://www.youtube.com/watch?v=gO65JJRqjuc)).\n- Join the [Discord](https://discord.gg/qcHUxb6deQ).\n- [Follow the official Twitter account](https://twitter.com/isographlabs).\n\n## What is Isograph?\n\nIsograph is a UI framework for building data-driven apps, currently limited to React and GraphQL data.\n\nIt has four goals:\n\n- to remove as much friction as possible from the process of building data-driven apps\n- to give developers the confidence that they won't break production\n- to make it easy to build performant apps\n- to expose powerful primitives so that developers can precisely model their domain\n\n## About Isograph: Fetching data and app structure\n\nLet's do a quick tour of how a basic Isograph app is constructed.\n\n### What is Isograph, and what are client fields?\n\nIsograph is a framework for building React applications that are backed by GraphQL data. In Isograph, components that read data can be selected from the graph, and automatically have the data they require passed in. Consider this example Avatar component:\n\n```jsx\nexport const Avatar = iso(`\n  field User.Avatar @component {\n    avatar_url\n  }\n`)(function AvatarComponent({ data }) {\n  return \u003cCircleImage image={data.avatar_url} /\u003e;\n});\n```\n\nThis defines a new client field named `Avatar`, which is then available on any GraphQL User. You might use this avatar field in another component, such as a button that navigates to a given user's profile.\n\n```jsx\nexport const UserProfileButton = iso(`\n  field User.UserProfileButton @component {\n    Avatar\n\n    # you can also select server fields, like in regular GraphQL:\n    id\n    name\n  }\n`)(function UserProfileButtonComponent({ data }) {\n  return (\n    \u003cButton onClick={() =\u003e navigateToUserProfile(data.id)}\u003e\n      {data.name}\n      \u003cdata.Avatar /\u003e\n    \u003c/Button\u003e\n  );\n});\n```\n\nThese calls to `iso` define client fields, which are functions from graph data (such as the user's name) to an arbitrary value. With Isograph, it's client fields all the way down — your entire app can be built in this way!\n\nNote what we didn't do:\n\n- The `Avatar` component didn't care how the `avatar_url` field was originally fetched. It just received it.\n- When writing the `UserProfileButton` component, we didn't import the `Avatar` client field\n- The `UserProfileButton` didn't pass any data down to the `Avatar`. It just rendered it! In fact, it didn't see or have access to any of the fields that the `Avatar` selected, so, changes to the fields that the `Avatar` selects will not change the behavior of other client fields!\n\n### How does Isograph fetch data?\n\nAt the root of each page, you will define an entrypoint. Isograph's compiler finds and processes all the entrypoints in your codebase, and will generate the appropriate GraphQL query.\n\nIf the compiler encounters ``iso(`entrypoint Query.UserList`);``, it would generate a query that would fetch all the server fields needed for the `Query.UserList` client field and all of the nested client fields that are reachable from that root.\n\nWe might set up a component to fetch that `UserList` data as follows:\n\n```jsx\nfunction UserListPageRoute() {\n  const queryVariables = {};\n  const { fragmentReference } = useLazyReference(\n    iso(`entrypoint Query.UserList`),\n    queryVariables,\n  );\n\n  const additionalRenderProps = {};\n  const Component = useResult(fragmentReference);\n  return \u003cComponent {...additionalRenderProps} /\u003e;\n}\n```\n\n\u003e Note that the call to `useResult(fragmentReference)` will suspend if the required data is not present in the store, so make sure that either `UserListPageRoute` is wrapped in a `React.Suspense` boundary, or that the `fragmentReference` is only read in a child component that is wrapped in a suspense boundary.\n\nNow, when `UserListPageRoute` is initially rendered, Isograph will make an API call.\n\n### How do components receive their data?\n\nYou may have noticed that when we rendered `\u003cdata.Avatar /\u003e`, we did not explicitly pass the data that the `Avatar` needs! Instead, when the component is rendered, Isograph will read the data that the `Avatar` component needs, and pass it to `Avatar`. The calling component:\n\n- only passes additional props that don't affect the query data, like `onClick`, and\n- does **not** know what data `Avatar` expects, and never sees the data that `Avatar` reads out. This is called **data masking**, and it's a crucial reason that teams of multiple developers can move quickly when building apps with Isograph: because no component sees the data that another component selected, changing one component cannot affect another!\n\n### Big picture\n\nAt the root of a page, you will define an entrypoint. For any such entrypoint, Isograph will:\n\n- Recursively walk it's dependencies and create a single GraphQL query that fetches **all** of the data reachable from this root.\n- When that page renders, or possibly sooner, Isograph will make the API call to fetch that data.\n- Each resolver will independently read the data that it specifically required.\n\n## About Isograph: `@loadable` fields\n\nSelections of client fields can be declared as `@loadable`, meaning that the data for that client field is not included as part of the parent request. Instead, the value that is read out contains a function that you can call to make a new network request for just the `@loadable` field. Consider:\n\n```jsx\nexport const UserDetailPage = iso(`\n  field User.UserDetailPage {\n    name\n    CreditCardInfo @loadable\n  }\n`)(({ data }) =\u003e {\n  const CreditCardInfo = useClientSideDefer(data.CreditCardInfo);\n\n  return (\n    \u003c\u003e\n      \u003ch1\u003eHello {data.name}\u003c/h1\u003e\n      \u003cReact.Suspense fallback=\"Loading credit card info\"\u003e\n        \u003cFragmentRenderer fragmentReference={CreditCardInfo} /\u003e\n      \u003c/React.Suspense\u003e\n    \u003c/\u003e\n  );\n});\n```\n\nIn this example, the `CreditCardInfo` component is slow to calculate. This might be because it has to make an API call to an external service. We would not like to slow down the entire page as a result of that. So, instead, label this field `@loadable`.\n\nNow, instead of returning a component that can be directly rendered, we get back something that contains a function that executes the network request to fetch the `CreditCardInfo`'s data. We pass that to `useClientSideDefer`, which makes that network request during the initial render of the component.\n\nThere we go! Now, our parent component can load quickly, and we make a follow-up request for the rest of the data!\n\n:::note\nYou are not expected to use the `@loadable` field directly. Instead, always pass it to a handler like `useClientSideDefer`.\n:::\n\n## About Isograph: `@exposeField`\n\n:::note\nThe `@exposeField` feature will change before the next release.\n:::\n\nTypes with the `@exposeField(field: String!, path: String!, fieldMap: [FieldMap!]!)` directive have their fields re-exposed on other objects. For example, consider this schema:\n\n```graphql\ninput SetUserNameParams {\n  id: ID!\n  some_other_param: String!\n}\n\ntype SetUserNameResponse {\n  updated_user: User!\n}\n\ntype Mutation\n  @exposeField(\n    field: \"set_user_name.updated_user\" # expose this field\n    fieldMap: [{ from: \"id\", to: \"id\" }] # mapping these fields\n    # as: \"custom_field_name\"\n  ) {\n  set_user_name(input: SetUserNameParams!): SetUserNameResponse!\n}\n```\n\nIn the above example, the `set_user_name` field will be made available on every `User` object, under the key `set_user_name` (which can be overridden with the `as` field). So, one could write a resolver:\n\n```jsx\nexport const UpdateUserNameButton = iso(`\n  field User.UpdateUserNameButton {\n    set_user_name\n  }\n`)(({ data }) =\u003e {\n  return (\n    \u003cdiv\n      onClick={() =\u003e data.set_user_name({ input: { new_name: 'Maybe' } })[1]()}\n    \u003e\n      Call me, maybe\n    \u003c/div\u003e\n  );\n});\n```\n\nClicking that button executes a mutation. The `id` field is automatically passed in (i.e. it comes from whatever `User` object where this field was selected.)\n\nThe fields that are refetched as part of the mutation response are whatever fields are selected on that user in the _merged_ query! So, if on that same `User`, we also (potentially through another resolver) selected the `name` field, the mutation response would include `name`! If, later, we selected `email`, it would also be fetched.\n\n## Getting involved and learning more\n\nThere's a lot more. These docs are threadbare.\n\n- See the sample apps in [`./demos`](./demos/).\n- Watch the [talk at GraphQL Conf](https://www.youtube.com/watch?v=gO65JJRqjuc).\n- Join the [Discord](https://discord.gg/qcHUxb6deQ).\n- [Follow the official Twitter account](https://twitter.com/isographlabs)\n\n## Other, older resources\n\n- See [the developer experience of using Isograph](https://www.youtube.com/watch?v=f1nfXc3VeTk).\n- Read [the substack article](https://isograph.substack.com/p/introducing-isograph).\n\n## Licensing\n\nIsograph is an open source software project and licensed under the terms of the MIT license.\n\n## Contributors\n\nThanks to all the contributors to Isograph!\n\n\u003ca href=\"https://github.com/isographlabs/isograph/graphs/contributors\"\u003e\n  \u003cimg src=\"https://contrib.rocks/image?repo=isographlabs/isograph\" /\u003e\n\u003c/a\u003e\n","funding_links":[],"categories":["Rust"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisographlabs%2Fisograph","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fisographlabs%2Fisograph","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisographlabs%2Fisograph/lists"}