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 to the codegen experimental feature:

export default config({
  graph: g,
  experimental: {
    codegen: true,
  },
})

Once the experimental feature is enabled, whenever you run grafbase dev, the types will be generated in a directory called generated 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"]
    }

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()
    },
  },
  experimental: { codegen: 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.

Code generation is still an experimental feature because we want to gather feedback on the developer experience and the generated code before we stabilize it. We'd love to hear your feedback and ideas, so join us on Discord.

Was this page helpful?