Pothos
Guide

Interfaces

Defining Interface Types

An interface defines fields that several object types have in common. Like objects, interfaces have a backing model that describes the data their field resolvers receive. You can use builder.interfaceRef to associate a TypeScript type with a GraphQL interface.

In this example, giraffes and lions have a diet, but each has some other data of its own:

import SchemaBuilder from '@pothos/core';

type Giraffe = {
  kind: 'giraffe';
  diet: string;
  heightInMeters: number;
};

type Lion = {
  kind: 'lion';
  diet: string;
  hasMane: boolean;
};

type Animal = Giraffe | Lion;

const builder = new SchemaBuilder({});
const AnimalRef = builder.interfaceRef<Animal>('Animal');
const GiraffeRef = builder.objectRef<Giraffe>('Giraffe');
const LionRef = builder.objectRef<Lion>('Lion');

AnimalRef.implement({
  fields: (t) => ({
    diet: t.exposeString('diet'),
  }),
  resolveType: (animal) => {
    switch (animal.kind) {
      case 'giraffe':
        return 'Giraffe';
      case 'lion':
        return 'Lion';
    }
  },
});

The resolveType function returns the GraphQL object type name to use when a field returns an Animal. Here, the kind property distinguishes the two shapes. It is part of our backing data, but isn't exposed as a GraphQL field.

implementing interfaces with object types

Use the interfaces option to specify which interfaces an object implements:

GiraffeRef.implement({
  interfaces: [AnimalRef],
  fields: (t) => ({
    height: t.exposeFloat('heightInMeters'),
  }),
});

LionRef.implement({
  interfaces: [AnimalRef],
  fields: (t) => ({
    hasMane: t.exposeBoolean('hasMane'),
  }),
});

Pothos adds the interface's fields to each implementing object, so both types have a diet field without defining it again. The object's backing model must be assignable to the interface's backing model so that the inherited field resolvers can use it. In this example, both Giraffe and Lion are members of the Animal TypeScript union.

Using an Interface as a return type

A field returning a list of animals can include both giraffes and lions:

builder.queryType({
  fields: (t) => ({
    animals: t.field({
      type: [AnimalRef],
      resolve: () => [
        { kind: 'giraffe', diet: 'herbivore', heightInMeters: 5.2 },
        { kind: 'lion', diet: 'carnivore', hasMane: true },
      ],
    }),
  }),
});

export const schema = builder.toSchema();

Querying interface fields

We can query diet on every animal. Fields that belong to a particular object type go inside an inline fragment. The __typename field tells us which object type each result has:

query {
  animals {
    __typename
    diet
    ... on Giraffe {
      height
    }
    ... on Lion {
      hasMane
    }
  }
}

The result includes the shared field and the matching fragment's fields for each animal:

{
  "data": {
    "animals": [
      { "__typename": "Giraffe", "diet": "herbivore", "height": 5.2 },
      { "__typename": "Lion", "diet": "carnivore", "hasMane": true }
    ]
  }
}

Other ways to resolve an interface

Using existing classes

If your application already uses classes for its data, prefer those classes as backing models. You can pass them to builder.interfaceType and builder.objectType, as described in the Objects guide.

Classes make automatic interface resolution straightforward. For the Giraffe class in that example, set isTypeOf: (value) => value instanceof Giraffe on its object type. GraphQL can then select the matching type without a resolveType function on the interface. Return actual class instances so the check succeeds.

Existing Error subclasses are another good use case: the Errors plugin matches errors against the configured classes using instanceof.

Another option is to return data with a __typename property containing the GraphQL object type's name, such as 'Giraffe'. GraphQL uses this property when the interface has no resolveType function. If an object also defines isTypeOf, that check must pass as well.

Interfaces implementing interfaces

Interfaces can also implement other interfaces using the interfaces option. See the SchemaBuilder API for the available options.

On this page