Search results, feeds and node lookups are all polymorphic.
An interface declares fields that several object types share; each type implements it and must include those fields:
interface Node { id: ID! }
type User implements Node { id: ID! name: String! }
type Post implements Node { id: ID! title: String! }A union says a value is one of several object types that need not share any fields: union SearchResult = User | Post | Tag.
Querying polymorphic fields:
- Fields declared on an interface can be selected directly.
- Type-specific fields need an inline fragment
... on Post { title }. - For a union you can only select
__typenamedirectly; everything else goes through fragments.
On the server, the runtime must know each value's concrete type. In graphql-tools style resolver maps you add __resolveType(obj) on the interface or union, returning the type name; alternatively types can define isTypeOf.
Choose an interface when types truly share a contract you will query generically; choose a union for "one of these unrelated things" such as search results or a mutation result that is either a success or one of several error types. Interfaces can also implement other interfaces.
Example
const typeDefs = `
interface Node { id: ID! }
type User implements Node { id: ID! name: String! }
type Post implements Node { id: ID! title: String! }
union SearchResult = User | Post
type Query { search(text: String!): [SearchResult!]! }
`;
const resolvers = {
SearchResult: {
__resolveType: (obj) => (obj.title ? "Post" : "User"),
},
};
// Query:
// { search(text: "graph") {
// __typename
// ... on User { name }
// ... on Post { title } } }
// -> [{"__typename":"Post","title":"Graph theory"},
// {"__typename":"User","name":"Grace"}]Remember
- Interface = shared fields; union = one of unrelated types
- Use
... on Typefor type-specific fields - Servers need
__resolveType(orisTypeOf) for abstract types
Watch out for
- Forgetting
__resolveType, which makes every polymorphic field fail at runtime.