{"id":13649257,"url":"https://github.com/acro5piano/typed-graphqlify","last_synced_at":"2025-05-15T16:05:32.663Z","repository":{"id":37743119,"uuid":"162337862","full_name":"acro5piano/typed-graphqlify","owner":"acro5piano","description":"Build Typed GraphQL Queries in TypeScript without the code generation","archived":false,"fork":false,"pushed_at":"2023-03-01T16:57:52.000Z","size":2925,"stargazers_count":648,"open_issues_count":22,"forks_count":28,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-05-14T18:14:16.700Z","etag":null,"topics":["graphql","javascript","javascript-library","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/typed-graphqlify","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/acro5piano.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"github":["acro5piano"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2018-12-18T19:46:09.000Z","updated_at":"2024-02-16T09:48:08.000Z","dependencies_parsed_at":"2024-01-13T14:43:56.898Z","dependency_job_id":"64750cf6-0b2c-42d6-af1c-3e20abb6a2dd","html_url":"https://github.com/acro5piano/typed-graphqlify","commit_stats":{"total_commits":391,"total_committers":14,"mean_commits":"27.928571428571427","dds":0.4552429667519181,"last_synced_commit":"e511013f8635548ed6a4040f8ac8a2d0cc06119d"},"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acro5piano%2Ftyped-graphqlify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acro5piano%2Ftyped-graphqlify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acro5piano%2Ftyped-graphqlify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acro5piano%2Ftyped-graphqlify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/acro5piano","download_url":"https://codeload.github.com/acro5piano/typed-graphqlify/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247569111,"owners_count":20959757,"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","javascript","javascript-library","typescript"],"created_at":"2024-08-02T01:04:53.460Z","updated_at":"2025-04-07T21:13:49.756Z","avatar_url":"https://github.com/acro5piano.png","language":"TypeScript","funding_links":["https://github.com/sponsors/acro5piano"],"categories":["TypeScript","JavaScript"],"sub_categories":[],"readme":"![release](https://github.com/acro5piano/typed-graphqlify/workflows/release/badge.svg)\n![test](https://github.com/acro5piano/typed-graphqlify/workflows/test/badge.svg)\n[![npm version](https://badge.fury.io/js/typed-graphqlify.svg)](https://badge.fury.io/js/typed-graphqlify)\n[![codecov](https://codecov.io/gh/acro5piano/typed-graphqlify/branch/master/graph/badge.svg)](https://codecov.io/gh/acro5piano/typed-graphqlify)\n\n![image](images/logo-fixed.png)\n\n# typed-graphqlify\n\nBuild Typed GraphQL Queries in TypeScript. A better TypeScript + GraphQL experience.\n\n# Install\n\n```\nnpm install --save typed-graphqlify\n```\n\nOr if you use Yarn:\n\n```\nyarn add typed-graphqlify\n```\n\n# Motivation\n\nWe all know that GraphQL is so great and solves many problems that we have with REST APIs, like overfetching and underfetching. But developing a GraphQL Client in TypeScript is sometimes a bit of pain. Why? Let's take a look at the example we usually have to make.\n\nWhen we use GraphQL library such as Apollo, We have to define a query and its interface like this:\n\n```typescript\ninterface GetUserQueryData {\n  getUser: {\n    id: number\n    name: string\n    bankAccount: {\n      id: number\n      branch?: string\n    }\n  }\n}\n\nconst query = graphql(gql`\n  query getUser {\n    user {\n      id\n      name\n      bankAccount {\n        id\n        branch\n      }\n    }\n  }\n`)\n\napolloClient.query\u003cGetUserQueryData\u003e(query).then(data =\u003e ...)\n```\n\nThis is so painful.\n\nThe biggest problem is the redundancy in our codebase, which makes it difficult to keep things in sync. To add a new field to our entity, we have to care about both GraphQL and TypeScript interface. And type checking does not work if we do something wrong.\n\n**typed-graphqlify** comes in to address this issues, based on experience from over a dozen months of developing with GraphQL APIs in TypeScript. The main idea is to have only one source of truth by defining the schema using GraphQL-like object and a bit of helper class. Additional features including graphql-tag, or Fragment can be implemented by other tools like Apollo.\n\n# How to use\n\nDefine GraphQL-like JS Object:\n\n```typescript\nimport { query, types, alias } from 'typed-graphqlify'\n\nconst getUserQuery = query('GetUser', {\n  user: {\n    id: types.number,\n    name: types.string,\n    bankAccount: {\n      id: types.number,\n      branch: types.optional.string,\n    },\n  },\n})\n```\n\nNote that we use our `types` helper to define types in the result.\n\nThe `getUserQuery` has `toString()` method which converts the JS object into GraphQL string:\n\n```typescript\nconsole.log(getUserQuery.toString())\n// =\u003e\n//   query getUser {\n//     user {\n//       id\n//       name\n//       bankAccount {\n//         id\n//         branch\n//       }\n//     }\n//   }\n```\n\nFinally, execute the GraphQL and type its result:\n\n```typescript\nimport { executeGraphql } from 'some-graphql-request-library'\n\n// We would like to type this!\nconst data: typeof getUserQuery.data = await executeGraphql(getUserQuery.toString())\n\n// As we cast `data` to `typeof getUserQuery.data`,\n// Now, `data` type looks like this:\n// interface result {\n//   user: {\n//     id: number\n//     name: string\n//     bankAccount: {\n//       id: number\n//       branch?: string\n//     }\n//   }\n// }\n```\n\n![image](https://user-images.githubusercontent.com/10719495/96347801-f5598180-10de-11eb-9283-78998a6a963e.png)\n\n# Features\n\nCurrently `typed-graphqlify` can convert these GraphQL features:\n\n- Operations\n  - Query\n  - Mutation\n  - Subscription\n- Inputs\n  - Variables\n  - Parameters\n- Data structures\n  - Nested object query\n  - Array query\n- Scalar types\n  - `number`\n  - `string`\n  - `boolean`\n  - Enum\n  - Constant\n  - Custom type\n  - Optional types, e.g.) `number | undefined`\n- Fragments\n- Inline Fragments\n\n# Examples\n\n## Basic Query\n\n```graphql\nquery getUser {\n  user {\n    id\n    name\n    isActive\n  }\n}\n```\n\n```typescript\nimport { query, types } from 'typed-graphqlify'\n\nquery('getUser', {\n  user: {\n    id: types.number,\n    name: types.string,\n    isActive: types.boolean,\n  },\n})\n```\n\nOr without query name\n\n```graphql\nquery {\n  user {\n    id\n    name\n    isActive\n  }\n}\n```\n\n```typescript\nimport { query, types } from 'typed-graphqlify'\n\nquery({\n  user: {\n    id: types.number,\n    name: types.string,\n    isActive: types.boolean,\n  },\n})\n```\n\n## Basic Mutation\n\nUse `mutation`. Note that you should use `alias` to remove arguments.\n\nNote: When `Template Literal Type` is supported officially, we don't have to write `alias`. See https://github.com/acro5piano/typed-graphqlify/issues/158\n\n```graphql\nmutation updateUserMutation($input: UserInput!) {\n  updateUser: updateUser(input: $input) {\n    id\n    name\n  }\n}\n```\n\n```typescript\nimport { mutation, alias } from 'typed-graphqlify'\n\nmutation('updateUserMutation($input: UserInput!)', {\n  [alias('updateUser', 'updateUser(input: $input)')]: {\n    id: types.number,\n    name: types.string,\n  },\n})\n```\n\nOr, you can also use `params` helper which is useful for inline arguments.\n\n```typescript\nimport { mutation, params, rawString } from 'typed-graphqlify'\n\nmutation('updateUserMutation', {\n  updateUser: params(\n    {\n      input: {\n        name: rawString('Ben'),\n        slug: rawString('/ben'),\n      },\n    },\n    {\n      id: types.number,\n      name: types.string,\n    },\n  ),\n})\n```\n\n## Nested Query\n\nWrite nested objects just like GraphQL.\n\n```graphql\nquery getUser {\n  user {\n    id\n    name\n    parent {\n      id\n      name\n      grandParent {\n        id\n        name\n        children {\n          id\n          name\n        }\n      }\n    }\n  }\n}\n```\n\n```typescript\nimport { query, types } from 'typed-graphqlify'\n\nquery('getUser', {\n  user: {\n    id: types.number,\n    name: types.string,\n    parent: {\n      id: types.number,\n      name: types.string,\n      grandParent: {\n        id: types.number,\n        name: types.string,\n        children: {\n          id: types.number,\n          name: types.string,\n        },\n      },\n    },\n  },\n})\n```\n\n## Array Field\n\nJust add array to your query. This does not change the result, but TypeScript will be aware the field is an array.\n\n```graphql\nquery getUsers {\n  users: users(status: \"active\") {\n    id\n    name\n  }\n}\n```\n\n```typescript\nimport { alias, query, types } from 'typed-graphqlify'\n\nquery('getUsers', {\n  [alias('users', 'users(status: \"active\")')]: [{\n    id: types.number,\n    name: types.string,\n  )],\n})\n```\n\n## Optional Field\n\nAdd `types.optional` or `optional` helper method to define optional field.\n\n```typescript\nimport { optional, query, types } from 'typed-graphqlify'\n\nquery('getUser', {\n  user: {\n    id: types.number,\n    name: types.optional.string, // \u003c-- user.name is `string | undefined`\n    bankAccount: optional({      // \u003c-- user.bankAccount is `{ id: number } | undefined`\n      id: types.number,\n    }),\n  },\n}\n```\n\n## Constant field\n\nUse `types.constant` method to define constant field.\n\n```graphql\nquery getUser {\n  user {\n    id\n    name\n    __typename # \u003c-- Always `User`\n  }\n}\n```\n\n```typescript\nimport { query, types } from 'typed-graphqlify'\n\nquery('getUser', {\n  user: {\n    id: types.number,\n    name: types.string,\n    __typename: types.constant('User'),\n  },\n})\n```\n\n## Enum field\n\nUse `types.oneOf` method to define Enum field. It accepts an instance of `Array`, `Object` and `Enum`.\n\n```graphql\nquery getUser {\n  user {\n    id\n    name\n    type # \u003c-- `STUDENT` or `TEACHER`\n  }\n}\n```\n\n```typescript\nimport { query, types } from 'typed-graphqlify'\n\nconst userType = ['STUDENT', 'TEACHER'] as const\n\nquery('getUser', {\n  user: {\n    id: types.number,\n    name: types.string,\n    type: types.oneOf(userType),\n  },\n})\n```\n\n```typescript\nimport { query, types } from 'typed-graphqlify'\n\nconst userType = {\n  STUDENT: 'STUDENT',\n  TEACHER: 'TEACHER',\n}\n\nquery('getUser', {\n  user: {\n    id: types.number,\n    name: types.string,\n    type: types.oneOf(userType),\n  },\n})\n```\n\nYou can also use `enum`:\n\n**Deprecated: Don't use enum, use array or plain object to define enum if possible. typed-graphqlify can't guarantee inferred type is correct.**\n\n```typescript\nimport { query, types } from 'typed-graphqlify'\n\nenum UserType {\n  'STUDENT',\n  'TEACHER',\n}\n\nquery('getUser', {\n  user: {\n    id: types.number,\n    name: types.string,\n    type: types.oneOf(UserType),\n  },\n})\n```\n\n## Field with arguments\n\nUse `params` to define field with arguments.\n\n```graphql\nquery getUser {\n  user {\n    id\n    createdAt(format: \"d.m.Y\")\n  }\n}\n```\n\n```typescript\nimport { query, types, params, rawString } from 'typed-graphqlify'\n\nquery('getUser', {\n  user: {\n    id: types.number,\n    createdAt: params({ format: rawString('d.m.Y') }, types.string),\n  },\n})\n```\n\n## Multiple Queries\n\nAdd other queries at the same level of the other query.\n\n```graphql\nquery getFatherAndMother {\n  father {\n    id\n    name\n  }\n  mother {\n    id\n    name\n  }\n}\n```\n\n```typescript\nimport { query, types } from 'typed-graphqlify'\n\nquery('getFatherAndMother', {\n  father: {\n    id: types.number,\n    name: types.string,\n  },\n  mother: {\n    id: types.number,\n    name: types.number,\n  },\n})\n```\n\n## Query Alias\n\nQuery alias is implemented via a dynamic property.\n\n```graphql\nquery getMaleUser {\n  maleUser: user {\n    id\n    name\n  }\n}\n```\n\n```typescript\nimport { alias, query, types } from 'typed-graphqlify'\n\nquery('getMaleUser', {\n  [alias('maleUser', 'user')]: {\n    id: types.number,\n    name: types.string,\n  },\n}\n```\n\n## Standard fragments\n\nUse the `fragment` helper to create GraphQL Fragment, and spread the result into places the fragment is used.\n\n```graphql\nquery {\n  user: user(id: 1) {\n    ...userFragment\n  }\n  maleUsers: users(sex: MALE) {\n    ...userFragment\n  }\n}\n\nfragment userFragment on User {\n  id\n  name\n  bankAccount {\n    ...bankAccountFragment\n  }\n}\n\nfragment bankAccountFragment on BankAccount {\n  id\n  branch\n}\n```\n\n```typescript\nimport { alias, fragment, query } from 'typed-graphqlify'\n\nconst bankAccountFragment = fragment('bankAccountFragment', 'BankAccount', {\n  id: types.number,\n  branch: types.string,\n})\n\nconst userFragment = fragment('userFragment', 'User', {\n  id: types.number,\n  name: types.string,\n  bankAccount: {\n    ...bankAccountFragment,\n  },\n})\n\nquery({\n  [alias('user', 'user(id: 1)')], {\n    ...userFragment,\n  },\n  [alias('maleUsers', 'users(sex: MALE)')], {\n    ...userFragment,\n  },\n}\n```\n\n## Inline Fragment\n\nUse `on` helper to write inline fragments.\n\n```graphql\nquery getHeroForEpisode {\n  hero {\n    id\n    ... on Droid {\n      primaryFunction\n    }\n    ... on Human {\n      height\n    }\n  }\n}\n```\n\n```typescript\nimport { on, query, types } from 'typed-graphqlify'\n\nquery('getHeroForEpisode', {\n  hero: {\n    id: types.number,\n    ...on('Droid', {\n      primaryFunction: types.string,\n    }),\n    ...on('Human', {\n      height: types.number,\n    }),\n  },\n})\n```\n\nIf you are using a discriminated union pattern, then you can use the `onUnion` helper, which will automatically generate the union type for you:\n\n```graphql\nquery getHeroForEpisode {\n  hero {\n    id\n    ... on Droid {\n      kind\n      primaryFunction\n    }\n    ... on Human {\n      kind\n      height\n    }\n  }\n}\n```\n\n```typescript\nimport { onUnion, query, types } from 'typed-graphqlify'\n\nquery('getHeroForEpisode', {\n  hero: {\n    id: types.number,\n    ...onUnion({\n      Droid: {\n        kind: types.constant('Droid'),\n        primaryFunction: types.string,\n      },\n      Human: {\n        kind: types.constant('Human'),\n        height: types.number,\n      },\n    }),\n  },\n})\n```\n\nThis function will return a type of `A | B`, meaning that you can use the following logic to differentiate between the 2 types:\n\n```typescript\nconst droidOrHuman = queryResult.hero\nif (droidOrHuman.kind === 'Droid') {\n  const droid = droidOrHuman\n  // ... handle droid\n} else if (droidOrHument.kind === 'Human') {\n  const human = droidOrHuman\n  // ... handle human\n}\n```\n\n## Directive\n\nDirective is not supported, but you can use `alias` to render it.\n\n```graphql\nquery {\n  myState: myState @client\n}\n```\n\n```typescript\nimport { alias, query } from 'typed-graphqlify'\n\nquery({\n  [alias('myState', 'myState @client')]: types.string,\n})\n```\n\nSee more examples at [`src/__tests__/index.test.ts`](https://github.com/acro5piano/typed-graphqlify/blob/master/src/__tests__/index.test.ts)\n\n# Usage with React Native\n\nThis library uses `Symbol` and `Map`, meaning that if you are targeting ES5 and lower, you will need to polyfill both of them.\n\nSo, you may need to import `babel-polyfill` in `App.tsx`.\n\n```typescript\nimport 'babel-polyfill'\nimport * as React from 'react'\nimport { View, Text } from 'react-native'\nimport { query, types } from 'typed-graphqlify'\n\nconst queryString = query({\n  getUser: {\n    user: {\n      id: types.number,\n    },\n  },\n})\n\nexport class App extends React.Component\u003c{}\u003e {\n  render() {\n    return (\n      \u003cView\u003e\n        \u003cText\u003e{queryString}\u003c/Text\u003e\n      \u003c/View\u003e\n    )\n  }\n}\n```\n\nSee: https://github.com/facebook/react-native/issues/18932\n\n# Why not use `apollo client:codegen`?\n\nThere are some GraphQL -\u003e TypeScript convertion tools. The most famous one is Apollo codegen:\n\nhttps://github.com/apollographql/apollo-tooling#apollo-clientcodegen-output\n\nIn this section, we will go over why `typed-graphqlify` is a good alternative.\n\nDisclaimer: I am not a heavy user of Apollo codegen, so the following points could be wrong. And I totally don't mean disrespect Apollo codegen.\n\n## Simplicity\n\nApollo codegen is a great tool. In addition to generating query interfaces, it does a lot of tasks including downloading schemas, schema validation, fragment spreading, etc.\n\nHowever, great usability is the tradeoff of complexity.\n\nThere are some issues to generate interfaces with Apollo codegen.\n\n- https://github.com/apollographql/apollo-tooling/issues/791\n- https://github.com/apollographql/apollo-tooling/issues/678\n\nI (and maybe everyone) don't know the exact reasons, but Apollo's codebase is too large to find out what the problem is.\n\nOn the other hand, `typed-graphqlify` is as simple as possible by design, and the logic is quite easy. If some issues happen, we can fix them easily.\n\n## Multiple Schemas problem\n\nCurrently Apollo codegen cannot handle multiple schemas.\n\n- https://github.com/apollographql/apollo-tooling/issues/588\n- https://github.com/apollographql/apollo-tooling/issues/554\n\nAlthough I know this is a kind of edge case, but if we have the same type name on different schemas, which schema is used?\n\n## typed-graphqlify works even without schema\n\nSome graphql frameworks, such as laravel-graphql, cannot print schema as far as I know.\nI agree that we should avoid to use such frameworks, but there must be situations that we cannot get graphql schema for some reasons.\n\n## Write GraphQL programmatically\n\nIt is useful to write GraphQL programmatically, although that is an edge case.\n\nImagine AWS management console:\n\n![image](https://user-images.githubusercontent.com/10719495/50487625-79420580-0a42-11e9-882f-2b5d571ebd13.png)\n\nIf you build something like that with GraphQL, you have to build GraphQL dynamically and programmatically.\n\ntyped-graphqlify works for such cases without losing type information.\n\n# Contributing\n\nTo get started with a development installation of the typed-graphqlify, follow the instructions at our [Contribution Guide](./CONTRIBUTING.md).\n\n# Thanks\n\nInspired by\n\n- https://github.com/kadirahq/graphqlify\n- https://github.com/19majkel94/type-graphql\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facro5piano%2Ftyped-graphqlify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Facro5piano%2Ftyped-graphqlify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facro5piano%2Ftyped-graphqlify/lists"}