Pothos
Guide

Objects

We'll define a GraphQL type for a giraffe, using a TypeScript interface to describe its data.

Defining an Object type

Create schema.ts with a builder and a reference for the Giraffe type:

import SchemaBuilder from '@pothos/core';

interface Giraffe {
  name: string;
  birthday: Date;
  heightInMeters: number;
}

const builder = new SchemaBuilder({});
const GiraffeRef = builder.objectRef<Giraffe>('Giraffe');

The interface describes the backing model: the data returned by resolvers for this type. objectRef associates that model with the GraphQL name Giraffe. We can use the ref when defining fields that return a giraffe.

Add some fields

Next, call implement to define the object's fields. It accepts the options for the object type, including an optional description:

GiraffeRef.implement({
  description: 'A giraffe in the zoo.',
  fields: (t) => ({
    name: t.exposeString('name'),
    height: t.exposeFloat('heightInMeters'),
    birthYear: t.int({
      resolve: (giraffe) => giraffe.birthday.getUTCFullYear(),
    }),
  }),
});

The fields function receives a field builder, usually named t. Here, t.exposeString('name') reads the name property from the backing model. The height field reads heightInMeters, giving that property a different name in the GraphQL schema.

For birthYear, we write a resolver that computes an integer from the giraffe's birthday. Pothos infers the resolver's first argument as Giraffe from the ref.

The backing model and GraphQL fields can have different shapes. Clients can query name, height, and birthYear; birthday is available to resolvers but isn't exposed as a field. We can add computed fields without adding properties to the backing model. The fields guide covers more field options.

Add a query

Add a field to the root Query type that returns a giraffe, then build the schema:

builder.queryType({
  fields: (t) => ({
    giraffe: t.field({
      type: GiraffeRef,
      resolve: () => ({
        name: 'James',
        birthday: new Date(Date.UTC(2012, 11, 12)),
        heightInMeters: 5.2,
      }),
    }),
  }),
});

export const schema = builder.toSchema();

The type: GiraffeRef option tells Pothos to check that the resolver returns data matching the Giraffe interface. That returned object becomes the first argument to the resolvers for name, height, and birthYear.

Create a server

builder.toSchema() builds a standard GraphQLSchema. To serve it with graphql-yoga, install graphql-yoga and save this as server.ts alongside schema.ts:

import { createServer } from 'node:http';
import { createYoga } from 'graphql-yoga';
import { schema } from './schema';

const yoga = createYoga({ schema });

const server = createServer(yoga);
server.listen(3000);

Query your data

Run npx tsx server.ts, then open http://localhost:3000/graphql and run this query:

query {
  giraffe {
    name
    birthYear
    height
  }
}

The result is:

{
  "data": {
    "giraffe": {
      "name": "James",
      "birthYear": 2012,
      "height": 5.2
    }
  }
}

Different ways to define Object types

This guide uses object refs with a TypeScript interface. You can also use an existing class or register backing models by name on the builder. These forms can be used together in the same schema.

Using Refs

You can create and implement a ref in one expression:

const GiraffeRef = builder.objectRef<Giraffe>('Giraffe').implement({
  fields: (t) => ({
    name: t.exposeString('name'),
  }),
});

For types that reference each other, TypeScript may need ref creation and implement to be separate statements, as in the walkthrough above. See circular references for details.

You can also pass a ref to builder.objectType(GiraffeRef, { ... }) with the same options.

Using classes

If your app already represents its data with classes, prefer using those classes as backing models. Pass the class to builder.objectType: its instance type becomes the backing model, and the class itself can be used as a field's type:

class Giraffe {
  constructor(
    public name: string,
    public birthday: Date,
    public heightInMeters: number,
  ) {}
}

builder.objectType(Giraffe, {
  name: 'Giraffe',
  fields: (t) => ({
    name: t.exposeString('name'),
  }),
});

builder.queryType({
  fields: (t) => ({
    giraffe: t.field({
      type: Giraffe,
      resolve: () => new Giraffe('James', new Date(Date.UTC(2012, 11, 12)), 5.2),
    }),
  }),
});

The name option supplies the GraphQL type name. When resolving an interface or union, you can also use an explicit isTypeOf: (value) => value instanceof Giraffe option to identify instances of the class.

Using SchemaTypes

You can declare backing models in SchemaTypes and pass it to SchemaBuilder. This lets you reference object types by their GraphQL names instead of importing refs. Using the Giraffe interface from above:

interface SchemaTypes {
  Objects: {
    Giraffe: Giraffe;
  };
}

const builder = new SchemaBuilder<SchemaTypes>({});

builder.objectType('Giraffe', {
  fields: (t) => ({
    name: t.exposeString('name'),
  }),
});

builder.queryType({
  fields: (t) => ({
    giraffe: t.field({
      type: 'Giraffe',
      resolve: () => ({
        name: 'James',
        birthday: new Date(Date.UTC(2012, 11, 12)),
        heightInMeters: 5.2,
      }),
    }),
  }),
});

The Objects mapping tells Pothos which backing model belongs to each name. Here, it types the Giraffe field resolvers and checks the object returned by the giraffe query. You still define the GraphQL fields with builder.objectType; declaring a backing model doesn't expose its properties. The SchemaBuilder guide covers the other type settings.

On this page