{"id":13719915,"url":"https://github.com/thomasheyenbrock/remix-graphql","last_synced_at":"2025-10-03T15:57:48.964Z","repository":{"id":37039066,"uuid":"443791640","full_name":"thomasheyenbrock/remix-graphql","owner":"thomasheyenbrock","description":"Utilities for using GraphQL with a Remix app","archived":false,"fork":false,"pushed_at":"2024-05-31T11:26:26.000Z","size":398,"stargazers_count":211,"open_issues_count":1,"forks_count":11,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-03-30T01:12:24.572Z","etag":null,"topics":["graphql","react","remix","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/thomasheyenbrock.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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}},"created_at":"2022-01-02T14:51:52.000Z","updated_at":"2025-02-01T16:00:14.000Z","dependencies_parsed_at":"2024-10-28T19:43:19.517Z","dependency_job_id":null,"html_url":"https://github.com/thomasheyenbrock/remix-graphql","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomasheyenbrock%2Fremix-graphql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomasheyenbrock%2Fremix-graphql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomasheyenbrock%2Fremix-graphql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thomasheyenbrock%2Fremix-graphql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thomasheyenbrock","download_url":"https://codeload.github.com/thomasheyenbrock/remix-graphql/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247423513,"owners_count":20936626,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["graphql","react","remix","typescript"],"created_at":"2024-08-03T01:00:57.657Z","updated_at":"2025-10-03T15:57:42.672Z","avatar_url":"https://github.com/thomasheyenbrock.png","language":"TypeScript","funding_links":[],"categories":["Utility","TypeScript"],"sub_categories":[],"readme":"# remix-graphql\n\n[Remix](https://remix.run) and [GraphQL](https://graphql.org) can live together\nin harmony ❤️ This package contains basic utility functions that can help you\nwith that.\n\nTo be more speciic, here's what the latest version of `remix-graphql` can help\nyou with:\n\n- Handling loader and action requests using GraphQL queries and mutations\n  - You can define a local schema and resolvers to handle the request\n  - You can also run perform GraphQL requests against a remote API\n- Setting up a GraphQL API as a [resource route](https://remix.run/docs/en/v1/guides/resource-routes)\n\nAnd here are some cool ideas what it might do as well in the future:\n\n- Batching queries from multiple loaders into a single API request\n\n## Contents\n\n- [Installing](#installing)\n  - [How to import from `remix-graphql`](#how-to-import-from-remix-graphql)\n- [Defining your schema](#defining-your-schema)\n- [Handle loader and action requests with GraphQL](#handle-loader-and-action-requests-with-graphql)\n  - [Automated type generation](#automated-type-generation)\n- [Send requests to a remote GraphQL API](#send-requests-to-a-remote-graphql-api)\n- [Set up a GraphQL API in a Remix app](#set-up-a-graphql-api-in-a-remix-app)\n- [Context](#context)\n  - [`request`](#request)\n  - [`redirect`](#redirect)\n\n## Installing\n\nYou can install `remix-graphql` with your preferred package manager. It depends\non the `graphql` package, so make sure to also have that installed.\n\n```sh\n# Using `npm`\nnpm install graphql remix-graphql\n# Or using `yarn`\nyarn add graphql remix-graphql\n```\n\nIt also lists some of the Remix-packages as peer dependencies. (If you used the\nRemix CLI to setup your project, you most likely have them installed already.)\nIf you get unexpected errors, double check that the following are installed:\n\n- `@remix-run/dev`\n- `@remix-run/react`\n- `@remix-run/serve`\n- `remix`\n\n### How to import from `remix-graphql`\n\nThis module not indended to be used in a browser environment, it only works\non the server. You can force the Remix compiler to never ever include stuff\nfrom `remix-graphql` in the client bundle by importing from a file with a\n`.server.js` (or `.server.ts`) extension.\n\n```ts\n// This will not work and will actually throw an error:\nimport { anything } from \"remix-graphql\";\n\n// Do this instead:\nimport { anything } from \"remix-graphql/index.server\";\n```\n\n## Defining your schema\n\n`remix-graphql` keeps it simple and let's you decide on the best way to define\nyour GraphQL schema. In all places where you need to \"pass your schema to\n`remix-graphql`\", the respective function expects a `GraphQLSchema` object.\n\nThat means all of the following approached work to define a schema:\n\n- Using the `GraphQLSchema` class from the `graphql` package (obviously...)\n- Defining the schema using the SDL, defining resolver functions in an object\n  and merging both with `makeExecutableSchema` (from `@graphql-tools/schema`)\n- Using `nexus` and `makeSchema`\n\nWe recommend exporting the schema from a file, e.g. `app/graphql/schema.server.ts`.\nBy using the `.server.ts` extension you make sure that none of this code will\nend up being shipped to the browser. (This is a hint to the Remix compiler that\nit should ignore this module when building the browser bundle.)\n\n## Handle loader and action requests with GraphQL\n\nBoth `loaders` and `actions` are just simple functions that return a `Response`\ngiven a `Request`. With `remix-graphql` you can use GraphQL to process this\nrequest! Here's a complete and working example of how it works:\n\n```tsx\n// app/routes/index.tsx\nimport type { GraphQLError } from \"graphql\";\nimport { Form } from \"remix\";\nimport type { ActionFunction, LoaderFunction } from \"@remix-run/node\";;\nimport { processRequestWithGraphQL } from \"remix-graphql/index.server\";\n\n// Import your schema from whereever you export it\nimport { schema } from \"~/graphql/schema\";\n\nconst ALL_POSTS_QUERY = /* GraphQL */ `\n  query Posts($limit: Int) {\n    posts(limit: $limit) {\n      id\n      title\n      likes\n      author {\n        name\n      }\n    }\n  }\n`;\n\nexport const loader: LoaderFunction = (args) =\u003e\n  processRequestWithGraphQL({\n    // Pass on the arguments that Remix passes to a loader function.\n    args,\n    // Provide your schema.\n    schema,\n    // Provide a GraphQL operation that should be executed. This can also be a\n    // mutation, it is named `query` to align with the common naming when\n    // sending GraphQL requests over HTTP.\n    query: ALL_POSTS_QUERY,\n    // Optionally provide variables that should be used for executing the\n    // operation. If this is not passed, `remix-graphql` will derive variables\n    // from...\n    // - ...the route params.\n    // - ...the submitted `formData` (if it exists).\n    variables: { limit: 10 },\n    // Optionally pass an object with properties that should be included in the\n    // execution context.\n    context: {},\n    // Optionally pass a function to derive a custom HTTP status code for a\n    // successfully executed operation.\n    deriveStatusCode(\n      // The result of the execution.\n      executionResult: ExecutionResult,\n      // The status code that would be returned by default, i.e. of the\n      // `deriveStatusCode` function is not passed.\n      defaultStatusCode: number\n    ) {\n      return defaultStatusCode;\n    },\n  });\n\nconst LIKE_POST_MUTATION = /* GraphQL */ `\n  mutation LikePost($id: ID!) {\n    likePost(id: $id) {\n      id\n      likes\n    }\n  }\n`;\n\n// The `processRequestWithGraphQL` function can be used for both loaders and\n// actions!\nexport const action: ActionFunction = (args) =\u003e\n  processRequestWithGraphQL({ args, schema, query: LIKE_POST_MUTATION });\n\nexport default function IndexRoute() {\n  const { data } = useLoaderData\u003cLoaderData\u003e();\n  if (!data) {\n    return \"Ooops, something went wrong :(\";\n  }\n\n  return (\n    \u003cmain\u003e\n      \u003ch1\u003eBlog Posts\u003c/h1\u003e\n      \u003cul\u003e\n        {data.posts.map((post) =\u003e (\n          \u003cli key={post.id}\u003e\n            {post.title} (by {post.author.name})\n            \u003cbr /\u003e\n            {post.likes} Likes\n            \u003cForm method=\"post\"\u003e\n              {/* `remix-graphql` will automatically transform all posted \n                  form data into variables of the same name for the GraphQL\n                  operation */}\n              \u003cinput hidden name=\"id\" value={post.id} /\u003e\n              \u003cbutton type=\"submit\"\u003eLike\u003c/button\u003e\n            \u003c/Form\u003e\n          \u003c/li\u003e\n        ))}\n      \u003c/ul\u003e\n    \u003c/main\u003e\n  );\n}\n\ntype LoaderData = {\n  data?: {\n    posts: {\n      id: string;\n      title: string;\n      likes: number;\n      author: { name: string };\n    }[];\n  };\n  errors?: GraphQLError[];\n};\n```\n\n### Automated type generation\n\nHidden at the end of the example above you see that the data returned from the\nloader function had to be typed by hand. Since GraphQL is strongly typed, you\ncan automate this if you want to!\n\nFirst, you need to generate the introspection data as JSON from your schema and\nstore it in a local file. For that you can create a simple script like this:\n\n```ts\n// app/graphql/introspection.{js,ts}\nimport fs from \"fs\";\nimport { introspectionFromSchema } from \"graphql\";\nimport path from \"path\";\nimport { schema } from \"./schema\";\n\nfs.writeFileSync(\n  path.join(__dirname, \"introspection.json\"),\n  JSON.stringify(introspectionFromSchema(schema))\n);\n```\n\nUsually you don't want to commit the generated JSON file to version control, so\nwe recommend to add it to your `.gitignore` file.\n\nTo make running this script easier, create a simple NPM script for it in your\n`package.json`:\n\n```json\n{\n  \"scripts\": {\n    // If you created the script with JavaScript\n    \"introspection\": \"node app/graphql/introspection.js\",\n    // If you created the script with TypeScript (make sure to install\n    // `esbuild-register` as dev-dependency in this case)\n    \"introspection\": \"node --require esbuild-register app/graphql/introspection.ts\"\n  }\n}\n```\n\nTo actually generate types from your queries and mutations we recommend using\n[GraphQL Code Generator](https://www.graphql-code-generator.com). For that you\nneed to install a couple of dependencies:\n\n```sh\n# Using `npm`\nnpm install --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations\n# Or using `yarn`\nyarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations\n```\n\nAlmost there! Now create a config file named `codegen.yml` in the root of your\nproject that contains the following:\n\n```yml\noverwrite: true\n# The path where the previously generated introspection data is stored\nschema: \"app/graphql/introspection.json\"\n# A glob that matches all files that contain operation definitions\ndocuments: \"app/routes/**/*.{ts,tsx}\"\ngenerates:\n  # This is the path where the generated types will be stored\n  app/graphql/types.ts:\n    plugins:\n      - \"typescript\"\n      - \"typescript-operations\"\n    config:\n      skipTypename: true\n```\n\nNow you can finally generate the types! For convenience, add another NPM\nscript:\n\n```json\n{\n  \"scripts\": {\n    \"introspection\": \"node --require esbuild-register app/graphql/introspection.ts\",\n    \"codegen\": \"npm run introspection \u0026\u0026 graphql-codegen --config codegen.yml\"\n  }\n}\n```\n\nRunning `npm run codegen` (or `yarn codegen`) will now automatically create\ntypes for the returned data for all queries and mutations. (Side-note: It's\nalso a great way to validate if all your operations are valid against your\nschema!)\n\n**One more thing:** Noticed the `/* GraphQL */` comment we included before the\nstrings that contain queries and mutations in the example above? This is\nimportant! It's a hint to `@graphql-codegen` that this string should be\nparsed as GraphQL. Without it you won't get any types for the operation\ndefined within the string.\n\nThe example above could now be modified like this:\n\n```ts\n// Add this import...\nimport type { PostsQuery } from \"~/graphql/types\";\n\n// ...and change the `LoaderData` type like this:\ntype LoaderData = { data?: PostsQuery; errors?: GraphQLError[] };\n```\n\n## Send requests to a remote GraphQL API\n\nMaybe you don't want to write your GraphQL API as part of your Remix app,\nor you want to use a third-party GraphQL API like GitHubs public API. In\nboth cases `remix-graphql` helps you with that!\n\n```tsx\n// app/routes/$username.tsx\nimport type { GraphQLError } from \"graphql\";\nimport type { LoaderFunction } from \"@remix-run/node\";\nimport { sendGraphQLRequest } from \"remix-graphql/index.server\";\n\nconst LOAD_USER_QUERY = /* GraphQL */ `\n  query LoadUser($username: String!) {\n    user(login: $username) {\n      name\n    }\n  }\n`;\n\nexport const loader: LoaderFunction = (args) =\u003e\n  sendGraphQLRequest({\n    // Pass on the arguments that Remix passes to a loader function.\n    args,\n    // Provide the endpoint of the remote GraphQL API.\n    endpoint: \"https://api.github.com/graphql\",\n    // Optionally add headers to the request.\n    headers: { authorization: `Bearer ${process.env.GITHUB_TOKEN}` },\n    // Provide the GraphQL operation to send to the remote API.\n    query: LOAD_USER_QUERY,\n    // Optionally provide variables that should be used for executing the\n    // operation. If this is not passed, `remix-graphql` will derive variables\n    // from...\n    // - ...the route params.\n    // - ...the submitted `formData` (if it exists).\n    // That means the following is the default and could also be ommited.\n    variables: args.params,\n  });\n\nexport default function UserRoute() {\n  const { data } = useLoaderData\u003cLoaderData\u003e();\n  if (!data) {\n    return \"Ooops, something went wrong :(\";\n  }\n  if (!data.user) {\n    return \"User not found :(\";\n  }\n  return \u003ch1\u003e{data.user.name}\u003c/h1\u003e;\n}\n\ntype LoaderData = {\n  data?: {\n    user: {\n      name: string | null;\n    } | null;\n  };\n  errors?: GraphQLError[];\n};\n```\n\nIf you want to do more stuff in your loader than just a single GraphQL query,\nyou can totally do that! The function `sendGraphQLRequest` will return the\n`Response` object from the fetch-request to the remote API, so you can do with\nthat whatever you need in your loader.\n\n```tsx\nimport { json } from \"remix\";\nimport type { LoaderFunction } from \"@remix-run/node\";\nimport { sendGraphQLRequest } from \"remix-graphql/index.server\";\n\nconst LOAD_USER_QUERY = /* GraphQL */ `\n  query LoadUser($username: String!) {\n    user(login: $username) {\n      name\n    }\n  }\n`;\n\nexport const loader: LoaderFunction = (args) =\u003e {\n  try {\n    const loadUserRes = await sendGraphQLRequest({\n      args,\n      endpoint: \"https://api.github.com/graphql\",\n      headers: { authorization: `Bearer ${process.env.GITHUB_TOKEN}` },\n      query: LOAD_USER_QUERY,\n    }).then((res) =\u003e res.json());\n\n    /* You can do any additional stuff here...  */\n    const otherStuff = 42;\n\n    return json({ username: loadUserRes.data.user.name, otherStuff });\n  } catch {\n    throw new Response(\"Something went wrong while loading the data :(\");\n  }\n};\n```\n\n## Set up a GraphQL API in a Remix app\n\nYou can create a dedicated endpoint for your GraphQL API using resource routes\nin Remix. All you need to do is create a route (e.g. `app/routes/graphql.ts`)\nand paste the following code. By using both a loader and an action your endpoint\nsupports both GET and POST requests!\n\n```ts\n// app/routes/graphql.ts\nimport {\n  getActionFunction,\n  getLoaderFunction,\n} from \"remix-graphql/index.server\";\nimport type { DeriveStatusCodeFunction } from \"remix-graphql/index.server\";\n\n// Import your schema from whereever you export it\nimport { schema } from \"~/graphql/schema\";\n\n// Handles GET requests\nexport const loader = getLoaderFunction({\n  // Provide your schema.\n  schema,\n  // Optionally pass an object with properties that should be included in the\n  // execution context.\n  context: {},\n  // Optionally pass a function to derive a custom HTTP status code for a\n  // successfully executed operation.\n  deriveStatusCode,\n});\n\n// Handles POST requests\nexport const action = getActionFunction({\n  // Provide your schema.\n  schema,\n  // Optionally pass an object with properties that should be included in the\n  // execution context.\n  context: {},\n  // Optionally pass a function to derive a custom HTTP status code for a\n  // successfully executed operation.\n  deriveStatusCode,\n});\n\n// This function equals the default behaviour.\nconst deriveStatusCode: DeriveStatusCodeFunction = (\n  // The result of the execution.\n  executionResult,\n  // The status code that would be returned by default, i.e. of the\n  // `deriveStatusCode` function is not passed.\n  defaultStatusCode\n) =\u003e defaultStatusCode;\n```\n\n## Context\n\nWhen defining a schema and writing resolvers, it's common to provide a context-\nobject. All functions exported by `remix-graphql` accept an optional property\n`context` in the arguments object. When passed, it must be an object. All of\nits properties will be included in the context object passed to your resolvers.\n\n`remix-graphql` also exports a `Context` type that contains all properties\nthat are added to this context objects for execution. This type accepts an\noptional generic by which you can add any custom properties to your context\nobject.\n\n```ts\nimport type { PrismaClient } from \"@prisma/client\";\nimport type { Context } from \"remix-graphql/index.server\";\n\ntype ContextWithDatabase = Context\u003c{ db: PrismaClient }\u003e;\n```\n\nThe following subsections highlight all properties that are added to the\ncontext object by `remix-graphql`.\n\n### `request`\n\nThis is the `Request` object that is passed to a loader- or action-function in\nRemix. It will always be part of the context object.\n\n### `redirect`\n\nWhen handling loaders or actions in UI routes, a common pattern in Remix is\nredirection. (Remix even provides a `redirect` utility function that can be\nreturned from any loader- or action-function.) In `remix-graphql` you can\nachieve this by using the `redirect` function that is provided in the context\nobject.\n\nThis function has the following signature:\n\n```ts\nfunction redirect(\n  // The URL for redirection\n  url: string,\n  // Optionally header values to include in the HTTP response\n  headers?: HeadersInit\n): void;\n```\n\nNote that this function is only part of the context object when handling\nGraphQL requests in UI routes, i.e. when using `processRequestWithGraphQL`.\nIt is _NOT_ part of the context object when handling GraphQL requests in a\nresource route, i.e. when using `getActionFunction` or `getLoaderFunction`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthomasheyenbrock%2Fremix-graphql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthomasheyenbrock%2Fremix-graphql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthomasheyenbrock%2Fremix-graphql/lists"}