diff --git a/cspell.yml b/cspell.yml index 671186ec40..1cb9ce9453 100644 --- a/cspell.yml +++ b/cspell.yml @@ -25,8 +25,10 @@ overrides: - swcrc - noreferrer - xlink + - codegen - composability - deduplication + - debuggable - subschema - subschemas - NATS diff --git a/website/pages/docs/_meta.ts b/website/pages/docs/_meta.ts index 0cb869e0c7..094e286afd 100644 --- a/website/pages/docs/_meta.ts +++ b/website/pages/docs/_meta.ts @@ -23,6 +23,7 @@ const meta = { 'oneof-input-objects': '', 'defer-stream': '', subscriptions: '', + 'type-generation': '', 'cursor-based-pagination': '', 'custom-scalars': '', 'advanced-custom-scalars': '', @@ -32,6 +33,10 @@ const meta = { 'graphql-errors': '', 'using-directives': '', '-- 3': { + type: 'separator', + title: 'Testing', + }, + '-- 4': { type: 'separator', title: 'FAQ', }, diff --git a/website/pages/docs/type-generation.mdx b/website/pages/docs/type-generation.mdx new file mode 100644 index 0000000000..fdcaf8e3a4 --- /dev/null +++ b/website/pages/docs/type-generation.mdx @@ -0,0 +1,263 @@ +--- +title: Type Generation for GraphQL +sidebarTitle: Type Generation +--- + +# Type Generation for GraphQL + +Writing a GraphQL server in JavaScript or TypeScript often involves managing complex +types. As your API grows, keeping these types accurate and aligned with your schema +becomes increasingly difficult. + +Type generation tools automate this process. Instead of manually defining or maintaining +TypeScript types for your schema and operations, these tools can generate them for you. +This improves safety, reduces bugs, and makes development easier to scale. + +This guide walks through common type generation workflows for projects using +`graphql-js`, including when and how to use them effectively. + +## Why use type generation? + +Type generation improves reliability and developer experience across the development +lifecycle. It's especially valuable when: + +- You want strong type safety across your server logic +- Your schema is defined separately in SDL files +- Your API surface is large, rapidly evolving, or used by multiple teams +- You rely on TypeScript for editor tooling, autocomplete, or static analysis + +By generating types directly from your schema, you can avoid drift between schema +definitions and implementation logic. + +## Code-first development + +In a code-first workflow, the schema is constructed entirely in JavaScript or TypeScript +using `graphql-js` constructors like `GraphQLObjectType`, `GraphQLSchema`, and others. +This approach is flexible and lets you build your schema programmatically using native +language features. + +If you're using this approach with TypeScript, you already get some built-in type safety +with the types exposed by `graphql-js`. For example, TypeScript can help ensure your resolver +functions return values that match their expected shapes. + +However, code-first development has tradeoffs: + +- You won't get automatic type definitions for your resolvers unless you generate +them manually or infer them through wrappers. +- Schema documentation, testing, and tool compatibility may require you to provide + a description of the schema in SDL first. + +You can still use type generation tools like GraphQL Code Generator in a code-first setup. +You just need to convert your schema into SDL. + +To produce an SDL description of your schema: + +```ts +import { printSchema } from 'graphql'; +import { schema } from './schema'; +import { writeFileSync } from 'fs'; + +writeFileSync('./schema.graphql', printSchema(schema)); +``` + +Once you've written the SDL, you can treat the project like an SDL-first project +for type generation. + +## Schema-first development + +In a schema-first workflow, your GraphQL schema is written in SDL, for example, `.graphql` +or `.gql` (discouraged) files. This serves as the source of truth for your server. This approach +emphasizes clarity because your schema is defined independently from your business logic. + +Schema-first development pairs well with type generation because the schema is +serializable and can be directly used by tools like [GraphQL Code Generator](https://the-guild.dev/graphql/codegen). + +With a schema-first workflow, you can: + +- Generate resolver type definitions and files that match your schema +- Generate operation types for client queries, integration tests, or internal tooling +- Detect breaking changes and unused types through schema diffing tools + +## Generating resolver types + +To get started, install the required packages: + +```bash +npm install graphql @graphql-codegen/cli @eddeee888/gcg-typescript-resolver-files +``` + +This scoped package is published by a community maintainer and is widely used for GraphQL server +type generation. + +We recommend using the [Server Preset](https://www.npmjs.com/package/@eddeee888/gcg-typescript-resolver-files) for a +managed workflow. It automatically generates types and files based on your schema without needing extra plugin setup. + +The Server Preset generates: + +- Resolver types, including parent types, arguments, return values, and context +- Resolver files with types wired up, ready for your business logic +- A resolver map and type definitions to plug into GraphQL servers + +This setup expects your schema is split into modules to improve readability and maintainability. For example: + +```text +├── src/ +│ ├── schema/ +│ │ ├── base/ +│ │ │ ├── schema.graphql +│ │ ├── user/ +│ │ │ ├── schema.graphql +│ │ ├── book/ +│ │ │ ├── schema.graphql +``` + +Here's an example `codegen.ts` file using the Server Preset: + +```ts filename="codegen.ts" +import type { CodegenConfig } from "@graphql-codegen/cli"; +import { defineConfig } from "@eddeee888/gcg-typescript-resolver-files"; + +const config: CodegenConfig = { + schema: "src/**/schema.graphql", + generates: { + "src/schema": defineConfig({ + resolverGeneration: "minimal", + }), + }, +}; + +export default config; +``` + +To generate the resolver types and files, run: + +```bash +npx graphql-codegen +``` + +This creates resolver files like: + +```ts filename="src/schema/user/resolvers/Query/user.ts" +import type { QueryResolvers } from "./../../../types.generated"; + +export const user: NonNullable = async ( + _parent, + _arg, + _ctx, +) => { + // Implement Query.user resolver logic here +}; +``` + +The user query resolver is typed to ensure that the user resolver expects an id argument and returns a +User, giving you confidence and autocomplete while implementing your server logic, which may look like this: + +```ts filename="src/schema/user/resolvers/Query/user.ts" +export const user: NonNullable = async ( + parent, + args, + context, +) => { + return context.db.getUser(args.id); +}; +``` + +See the official [Server Preset guide](https://the-guild.dev/graphql/codegen/docs/guides/graphql-server-apollo-yoga-with-server-preset) to learn about its other features, including mappers convention and static analysis for runtime safety. + +## Generating operation types + +In addition to resolver types, you can generate types for GraphQL operations such as queries, mutations, and +fragments. This is especially useful for shared integration tests or client logic that needs to match the schema +precisely. + +To get started, install the required packages: + +```bash +npm install graphql @graphql-codegen/cli +``` + +We recommend using the GraphQL Code Generator [Client Preset](https://the-guild.dev/graphql/codegen/plugins/presets/preset-client) for a managed workflow: + +- Write operations with GraphQL syntax in the same file where it is used +- Get type-safety when using the result + +Here's an example configuration using the Client Preset: + +```ts filename="codegen.ts" +import type { CodegenConfig } from "@graphql-codegen/cli"; + +const config: CodegenConfig = { + schema: "src/**/schema.graphql", + documents: ["src/**/*.ts"], + ignoreNoDocuments: true, + generates: { + "./src/graphql/": { + preset: "client", + config: { + documentMode: "string", + }, + }, + }, +}; + +export default config; +``` + +To keep generated types up to date as you edit your code, run the generator in watch mode: + +```bash +npx graphql-codegen --config codegen.ts --watch +``` + +Once generated, import the `graphql` function from `src/graphql/` to write GraphQL operations +directly in your TypeScript files: + +```ts filename="src/index.ts" +import { graphql } from "./graphql"; + +const UserQuery = graphql(` + query User($id: ID!) { + user(id: ID!) { + id + fullName + } + } +`); + +const response = await fetch("https://graphql.org/graphql/", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/graphql-response+json", + }, + body: JSON.stringify({ + query: UserQuery, + variables: { id: "1" }, + }), +}); + +if (!response.ok) { + throw new Error("Network response was not ok"); +} + +const result: ResultOf = await response.json(); + +console.log(result); +``` + +For guides on using the Client Preset with popular frameworks and tools, see: + +- [Vanilla TypeScript](https://the-guild.dev/graphql/codegen/docs/guides/vanilla-typescript) +- [React Query](https://the-guild.dev/graphql/codegen/docs/guides/react-query) +- [React / Vue](https://the-guild.dev/graphql/codegen/docs/guides/react-vue) + +## Best practices for CI and maintenance + +To keep your type generation reliable and consistent: + +- Check in generated files to version control so teammates and CI systems don't produce +divergent results. +- Run type generation in CI to ensure types stay in sync with schema changes. +- Use schema diffing tools like `graphql-inspector` to catch breaking changes before +they're merged. +- Automate regeneration with pre-commit hooks, GitHub Actions, or lint-staged workflows. \ No newline at end of file