Pothos v4 is now available! 🎉Check out the full migration guide here

Pothos

Objects

This will walk you through creating your first object types, some concepts in this guide will be explained further in later guides.

Defining an Object type

When adding a new type to your schema, you'll need to figure how the data behind this type will be represented. Pothos entirely decouples your data from your GraphQL schema, and has many different ways to implement Objects in your schema.

In this guide, we will be implementing a Giraffe object type:

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

The easiest way to create a new Object based in an existing Typescript type is wit the objectRef method:

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

This will create a new ObjectRef that can be used to reference the Giraffe type in other parts of the schema. By passing in the Giraffe interface, We give the ObjectRef the information it needs to ensure that fields we add to the Giraffe type are type-safe, and that any fields that reference the Giraffe Field return the expected data.

Next, We can need to add an implementation for the Giraffe type:

const GiraffeRef = builder.objectRef<Giraffe>('Giraffe');
 
GiraffeRef.implement({
  description: 'Long necks, cool patterns, taller than you.',
  fields: (t) => ({}),
});

In the implementation, we can add a description (optional) and a function to define the fields available to query on the Giraffe type.

Add some fields

The fields function receives a FieldBuilder instance that can be used to define the fields for your type. the FieldBuilder will be covered in more details in the fields guide.

GiraffeRef.implement(Giraffe, {
  fields: (t) => ({
    name: t.exposeString('name'),
    height: t.exposeFloat('heightInMeters'),
    age: t.int({
      resolve: (parent) => {
        // Do some date math to get an approximate age from a birthday
        const ageDifMs = Date.now() - parent.birthday.getTime();
        const ageDate = new Date(ageDifMs); // milliseconds from epoch
        return Math.abs(ageDate.getUTCFullYear() - 1970);
      },
    }),
  }),
});

You'll notice that we haven't added any additional typescript definitions when defining our fields. Pothos will uses the type provided to objectRef to ensure that the fields we add to the Giraffe type are type-safe. This type is only used to ensure that the implementation is type-safe, but Pothos we never automatically expose properties from the underlying data without an explicit field definition.

In the example above, we have examples of "exposing" data from the underlying type, ad well as field that requires some additional logic to resolve.

Add a query

We can create a root Query object with a field that returns a giraffe using builder.queryType

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

We can use the ObjectRef created earlier as the type option when defining fields that return the Giraffe type.

Create a server

Pothos schemas build into a plain schema that uses types from the graphql package. This means it should be compatible with most of the popular GraphQL server implementations for node. In this guide we will use graphql-yoga but you can use whatever server you want.

import { createServer } from 'http';
import { createYoga } from 'graphql-yoga';
 
const yoga = createYoga({
  schema: builder.toSchema(),
  context: (ctx) => ({
    user: { id: Number.parseInt(ctx.request.headers.get('x-user-id') ?? '1', 10) },
  }),
});
 
export const server = createServer(yoga);
 
server.listen(3000);
 
// Build schema and start server with the types we wrote above
const server = createServer({
  schema: builder.toSchema(),
});
 
server.start();

Query your data

  1. Run your server (either with ts-node) by compiling your code and running it with node.
  2. Open http://0.0.0.0:3000/graphql to open the playground and query your API:
query {
  giraffe {
    name
    age
    height
  }
}

Different ways to define Object types

There are many different ways that you can provide type information to Pothos about what the underlying data in your graph will be. Depending on how the rest of your application is structured you can pick the approach that works best for you, or use a combination of different styles.

Using Refs

ObjectRefs (the method shown above) is the most flexible solution, and makes it easy to integrate pothos with data sources that have their own Typescript types.

Object refs can be created using builder.objectRef, and then implemented by calling the implement method on the ref, or by passing the ref to builder.objectType:

const GiraffeRef = builder.objectRef<GireffeType>('Giraffe').implement({
  description: 'Long necks, cool patterns, taller than you.',
  fields: (t) => ({}),
});

When using objectRefs with circular dependencies, ensure that the implement method is called as a separate statement, or typescript may complain about circular references:

Using classes

If your data is already represented as a class, Pothos supports using the classes themselves as ObjectRefs. This allows you to define a type-safe schema with minimal typescript definitions.

export class Giraffe {
  name: string;
  birthday: Date;
  heightInMeters: number;
 
  constructor(name: string, birthday: Date, heightInMeters: number) {
    this.name = name;
    this.birthday = birthday;
    this.heightInMeters = heightInMeters;
  }
}
 
builder.objectType(Giraffe, {
  // Name is required when using a class as an ObjectRef
  name: 'Giraffe',
  description: 'Long necks, cool patterns, taller than you.',
  fields: (t) => ({}),
});
 
builder.queryFields((t) => ({
  giraffe: t.field({
    type: Giraffe,
    resolve: () => new Giraffe('James', new Date(Date.UTC(2012, 11, 12)), 5.2),
  }),
}));

Using SchemaTypes

You can also provide type mappings when you create the SchemaBuilder, which allows you to reference the types by name throughout your schema (as a string).

const builder = new SchemaBuilder<{ Objects: { Giraffe: GiraffeType } }>({});
 
builder.objectType('Giraffe', {
  description: 'Long necks, cool patterns, taller than you.',
  fields: (t) => ({}),
});
 
builder.queryFields((t) => ({
  giraffe: t.field({
    type: 'Giraffe',
    resolve: () => ({
      name: 'James',
      birthday: new Date(Date.UTC(2012, 11, 12)),
      heightInMeters: 5.2,
    }),
  }),
}));

This is ideal when you want to list out all the types for your schema in one place, or you have interfaces/types that define your data rather than classes, and means you won't have to import anything when referencing the object type in other parts of the schema.

The type signature for SchemaBuilder is described in more detail later, for now, it is enough to know that the Objects type provided to the schema builder allows you to map the names of object types to type definitions that describe the data for those types.

On this page