Code generation

Grafbase can generate TypeScript types for your custom resolvers. Codegen takes into account all types, including those from connectors, for example when you use the Postgres or OpenAPI connectors and you extend types coming from your database or your API.

To enable the feature, you have to opt-in in your Grafbase configuration:

export default config({ graph: g, codegen: { enabled: true, // Optional: define the directory path where you want the generated code to be written, relative to your grafbase configuration. Default: "generated". path: 'generated', }, })

Once the feature is enabled, whenever you run grafbase dev, the types will be generated in a directory called generated (or any directory you configured with the path argument) next to your Grafbase configuration.

❯ tree . . ├── generated │   └── index.ts ├── grafbase.config.ts ├── node_modules │   └── @grafbase │   └── sdk -> ../.pnpm/@grafbase+sdk@0.15.0/node_modules/@grafbase/sdk ├── package.json ├── package-lock.json ├── pnpm-lock.yaml ├── tsconfig.json └── README.md

You can decide to commit these types or put the generated directory in your .gitignore. Both options work fine once you deploy.

We advise you to add the following snippet to your tsconfig.json inside "compilerOptions", so you can import the generated types from @grafbase/ generated:

"compilerOptions": { "paths": { "@grafbase/generated": ["./generated"] }

Change the import name if you prefer another name, and make the path match the directory you configured in path if you are not using the default.

Then you are ready use types in your resolvers. In this example, we'll extend a type from the Stripe API, using the OpenAPI connector.

import { config, connector, graph } from '@grafbase/sdk' const g = graph.Standalone() const stripe = connector.OpenAPI('Stripe', { schema: 'https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json', headers: headers => { headers.set('Authorization', { forward: 'Authorization' }) }, transforms: { queryNaming: 'OPERATION_ID' }, }) g.extend('StripeAccountBusinessProfile', t => { t.addField( 'isRecurringCustomer', g.boolean().resolver('customer/is_recurring'), ) }) g.datasource(stripe) export default config({ graph: g, cache: { rules: [ { types: ['Query'], maxAge: 60, }, ], }, auth: { rules: rules => { rules.public() }, }, codegen: { enabled: true }, })

When you write the resolver at resolvers/customer/is_recurring.ts, you only need one type annotation to make your resolver fully typed, including checking of argument and return types, as well as autocompletion.

import { Resolver } from '@grafbase/generated' const resolver: Resolver['StripeAccountBusinessProfile.isRecurringCustomer'] = ( parent, args, context, ) => { // TODO: implement the customerIsRecurring resolver return true } export default resolver

When you start typing const resolver: Resolver[', you get autocompletion on the names of the custom resolvers. Once that is inserted, all the arguments (parent, args, context) and the return type are strongly typed.

In addition, the grafbase dev command uses the type annotation to double check that you are using the right generated type in the right resolver file, and that the resolver file corresponds to the right field in your configuration.

We'd love to hear your feedback, ideas and feature requests, so join us on Discord.

Was this page helpful?