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.