Interfaces in GraphQL define a common set of fields that multiple object types must implement. They allow a field to return different object types through a shared interface, while clients can query common fields and request type-specific fields when needed.
- An object type that implements an interface must provide all its fields with compatible types.
- Clients can use inline fragments to query fields specific to a concrete object type.
- Interfaces are output types and cannot be used directly as mutation input types.
Defining Interfaces in GraphQL Schema
An interface is defined using the interface keyword, followed by its name and field definitions. Object types use the implements keyword to implement that interface.
Example: A GraphQL API can define an Animal interface shared by Dog and Cat types.
interface Animal {
id: ID!
name: String!
species: String!
}
type Dog implements Animal {
id: ID!
name: String!
species: String!
breed: String!
}
type Cat implements Animal {
id: ID!
name: String!
species: String!
color: String!
}
- Both Dog and Cat implement the fields declared by Animal. Each type also defines its own additional field: breed for Dog and color for Cat.
- The implementing object types must define every interface field with a compatible return type. An interface can also implement another interface when the required fields and relationships are satisfied.
Querying Interface Fields
A field that returns an interface allows clients to query fields declared by that interface. Inline fragments provide access to fields available only on specific implementing object types.
Example: Extend the schema with a query field
type Query {
animals: [Animal!]!
}
A client can request common and type-specific fields in the same operation:
query {
animals {
id
name
species
... on Dog {
breed
}
... on Cat {
color
}
}
}
The id, name, and species fields are available for every Animal. The breed field is requested only when the returned object is a Dog, while color is requested only for a Cat.
An example response is:
{
"data": {
"animals": [
{
"id": "1",
"name": "Buddy",
"species": "Dog",
"breed": "Labrador"
},
{
"id": "2",
"name": "Luna",
"species": "Cat",
"color": "White"
}
]
}
}
The response illustrates how one interface field can return objects of different concrete types. The actual values depend on the server's resolver implementation.
Resolving Interfaces in GraphQL
When a field returns an interface, the GraphQL server must determine which implementing object type each returned value represents. In Apollo Server, this can be done with the interface's __resolveType resolver.
Example: The following resolver map identifies animals by their object properties
const resolvers = {
Animal: {
__resolveType(animal) {
if (Object.hasOwn(animal, 'breed')) {
return 'Dog';
}
if (Object.hasOwn(animal, 'color')) {
return 'Cat';
}
return null;
}
},
Query: {
animals: () => [
{
id: '1',
name: 'Buddy',
species: 'Dog',
breed: 'Labrador'
},
{
id: '2',
name: 'Luna',
species: 'Cat',
color: 'White'
}
]
}
};
- The __resolveType function returns the name of the concrete GraphQL object type. Returning null for an object that cannot be resolved results in a GraphQL execution error.
- This example uses Apollo Server's resolver-map format. A complete application must combine the resolver map with the schema definitions and configure its GraphQL server.
Using Interfaces with Mutations
Interfaces cannot be used directly as input types because GraphQL interfaces are output types. Mutations that accept structured input must use an input object type declared with the input keyword.
For example:
input AddUserInput {
name: String!
age: Int!
}
type User {
id: ID!
name: String!
age: Int!
}
type Mutation {
addUser(input: AddUserInput!): User!
}
Here, AddUserInput defines the fields accepted by the addUser mutation. It is an input object, not an interface. The User output type can implement a separate interface if the schema requires a common set of fields.
Benefits of Interfaces in GraphQL
- Shared field definitions: Multiple object types can expose a consistent set of fields.
- Flexible queries: Clients can retrieve common fields and request type-specific fields using inline fragments.
- Clear schema contracts: Implementing object types must satisfy the interface's field requirements.
- Polymorphic responses: A single field can return different implementing object types through a common interface.