Interfaces, Unions and Enums

advanced
12 min

Interfaces, Unions and Enums

Real domains are polymorphic: a feed mixes posts and photos, a search returns users and articles, a status field allows only a handful of values. GraphQL models all of this precisely with three type-system features: interfaces, unions, and enums. Used well, they make illegal states unrepresentable in your API.

Enums: A Closed Set of Values

An enum restricts a field to a fixed list of symbolic values:

graphql
enum PostStatus { DRAFT PUBLISHED ARCHIVED } type Post { status: PostStatus! }

Compared with a String, an enum is validated on the way in (a mutation passing "pubished" fails before your resolver runs), documented automatically, and generates real union/enum types in TypeScript codegen. Convention is SCREAMING_SNAKE_CASE. Adding a value is backward compatible for the server but remember old clients may not handle it, so exhaustive switch statements on the client should always include a default branch.

Interfaces: Shared Fields with a Contract

An interface defines fields that several object types guarantee to implement:

graphql
interface Node { id: ID! } interface Content { id: ID! createdAt: String! author: User! } type Post implements Node & Content { id: ID! createdAt: String! author: User! title: String! } type Photo implements Node & Content { id: ID! createdAt: String! author: User! url: String! } type Query { feed: [Content!]! }

Querying an interface, you can select shared fields directly and use inline fragments for type-specific ones:

graphql
query Feed { feed { __typename id createdAt ... on Post { title } ... on Photo { url } } }

__typename is a meta-field available on any selection; it returns the concrete type name and is how clients branch on the result.

Unions: Either/Or with Nothing Shared

A union says a field returns one of several types that need not share any fields:

graphql
union SearchResult = User | Post | Photo type Query { search(term: String!): [SearchResult!]! }

Because there are no guaranteed common fields, every data field must sit inside an inline fragment (only __typename is directly selectable). Choose an interface when types genuinely share a contract; choose a union when they are simply alternatives. A particularly powerful pattern is using unions for mutation results: union CreatePostResult = Post | ValidationError | RateLimited forces clients to handle each outcome explicitly.

Resolving Abstract Types on the Server

The server must be able to tell which concrete type a runtime object is. In Apollo Server you supply __resolveType:

js
const resolvers = { Content: { __resolveType(obj) { if (obj.title !== undefined) return "Post"; if (obj.url !== undefined) return "Photo"; return null; // triggers a GraphQL error }, }, };

Many teams instead store an explicit kind discriminator on records to keep this logic trivial.

Key Takeaways

  • Enums validate closed value sets at the API boundary and power exact codegen types.
  • Interfaces guarantee shared fields; query them directly and add inline fragments for specifics.
  • Unions model pure alternatives, including expressive typed mutation errors.
  • __typename plus __resolveType are the client and server halves of runtime type dispatch.

Next lesson: Consuming GraphQL from a Frontend, from raw fetch to Apollo Client.

Interfaces, Unions and Enums - GraphQL | CodeYourCraft | CodeYourCraft