Resolvers

Resolvers extend your GraphQL API and can be used to run custom business logic.

You can build locally using the Grafbase CLI and control access to resolvers using auth rules. Resolvers automatically deployed when you push to GitHub.

Resolvers can be added to extend connected APIs, or the schema with query and mutation resolvers.

Resolvers are made up of two major components:

  • The field definition with a resolver property
  • The resolver function implementation inside resolvers

Inside grafbase.config.ts add the query hello:

import { g } from '@grafbase/sdk' g.query('hello', { args: { name: g.string().optional() }, returns: g.string(), resolver: 'hello', })

Add the following to the file resolvers/hello.js:

export default function Resolver(_, { name }) { return `Hello ${name || 'world'}!` }

Now run the Grafbase CLI to start the local development server:

npx grafbase dev

Finally execute the following GraphQL query:

query { hello(name: "Grafbase") }

You must declare any input or return types and pass the name of the resolver to connect.

import { g } from '@grafbase/sdk' const user = g.type('User', { name: g.string().optional(), email: g.email().optional(), }) g.extend(user, { gravatar: { returns: g.url(), resolver: 'user/gravatar', }, })

All resolvers must be placed inside the folder resolvers.

You can also use folders to separate your resolver types for queries, mutations, and fields.

. └── package.json └── resolvers └── user └── gravatar.js └── grafbase.config.ts

Resolver functions have the following arguments in this order:

  1. root: The root of the current query/mutation/field
  2. args: The arguments provided to the query/mutation/field
  3. context: The context from the Grafbase server/project
  4. info: The execution information for the current operation
export default function Resolver(root, args, context) { // return ... }

The first parameter root (sometimes called parent) is the result of the previous call.

import { g } from '@grafbase/sdk' const user = g.model('User', { name: g.string().optional(), email: g.email().optional(), gravatar: g.url().optional().resolver('user/gravatar'), })

The second parameter passed to the resolver function is known as args.

These arguments are defined by you and work with query, mutation and field resolvers.

import { g } from '@grafbase/sdk' const checkoutLineItem = g.input('CheckoutLineItem', { price: g.string(), quantity: g.int().default(1), }) const checkoutSessionInput = g.input('CheckoutSessionInput', { lineItems: g.inputRef(checkoutLineItem).list(), }) const checkoutSession = g.type('CheckoutSession', { url: g.url() sum: g.int().resolver('sum').arguments({ a: g.int().optional().default(0), b: g.int().optional().default(0) }) }) g.query('sum', { args: { a: g.int().optional().default(0), b: g.int().optional().default(0) }, returns: g.int(), resolver: 'sum', }) g.mutation('checkout', { args: { input: g.inputRef(checkoutSessionInput) }, returns: g.ref(checkoutSession), resolver: 'checkout', })

The third argument referred to as context contains important information from Grafbase about the request.

You can obtain the forwarded request headers from inside the context argument:

export default function Resolver(_, __, { request }) { const { headers } = request // ... }

This is useful if you need to use the current Authorization header from the user management platform with whatever next action you take inside the resolver.

The context also exposes Grafbase KV that you can use.

The fourth argument passed to the resolver function is called info. We don't currently expose the full GraphQLResolverInfo object but instead a limited set:

  • fieldName (String) — The name of the field that called the resolver.
  • path (ResponsePath) — The fields traversed prior to the called resolver.
  • variableValues ({ [variableName: string]: mixed }) — A map of any variables passed to the query.
export default function Resolver(_, __, ___, info) { const { fieldName, path, variableValues } = info // ... }

The Query type is at the root of your GraphQL API which contains any auto-generated queries by the Grafbase Database, and also any custom resolvers you have added.

import { g } from '@grafbase/sdk' g.query('hello', { returns: g.string(), resolver: 'hello-world', })

Mutations are used when you want to mutate data. The GraphQL API contains mutations auto-generated by the Grafbase Database for all models and any custom resolvers you have added.

import { g } from '@grafbase/sdk' g.mutation('say', { args: { word: g.string() }, returns: g.string(), resolver: 'say', })

Resolvers can be attached to fields and fields can access the data stored inside the model using the first argument (root):

import { g } from '@grafbase/sdk' const location = g.type('Location', { latitude: g.float(), longitude: g.float(), }) const place = g.type('Place', { name: g.string().optional(), location: g.ref(location).optional(), }) g.extend(place, { weather: { returns: g.float().optional(), resolver: 'place/weather', }, })

Grafbase is designed to automatically conceal any errors that occur within resolvers, ensuring that internal operations remain hidden from the client. However, in certain scenarios, it may be necessary to reveal expected errors.

To accomplish this, you can throw a GraphQLError from the graphql package.

import { GraphQLError } from 'graphql' function Resolver(_, { args }) { const { number } = args if (number >= 10) { throw new GraphQLError( `You must provide a number less than 10. You passed ${number}!`, ) } // ... }

Make sure you have graphql installed as a dependency.

Resolvers can use environment variables set using the CLI or in your project settings.

import { g } from '@grafbase/sdk' g.query('hello', { returns: g.string(), resolver: 'hello', })

Make sure the .env file is inside your project's grafbase folder.

You should have a package.json inside your project root folder to install dependencies to be used by resolvers.

import { g } from '@grafbase/sdk' const address = g.type('Address', { line1: g.string().optional(), city: g.string().optional(), country: g.string().optional(), }) const customer = g.type('Customer', { id: g.id(), balance: g.int().optional(), email: g.email().optional(), address: g.ref(address).optional(), }) g.query(place, { weather: { returns: g.ref(customer).list(), resolver: 'customers', }, })

Resolvers can be written in TypeScript (.ts) and JavaScript (.js).

You can enable caching for resolvers using the cache config:

import { g } from '@grafbase/sdk' const user = g.model('User', { name: g.string().optional(), email: g.email().optional(), gravatar: g .url() .optional() .resolver('user/gravatar') .cache({ maxAge: 60, staleWhileRevalidate: 60 }), })
Was this page helpful?