|
| 1 | +--- |
| 2 | +title: Abstract types in GraphQL.js |
| 3 | +--- |
| 4 | + |
| 5 | +GraphQL includes two kinds of abstract types: interfaces and unions. These types let a single |
| 6 | +field return values of different object types, while keeping your schema type-safe. |
| 7 | + |
| 8 | +This guide covers how to define and resolve abstract types using GraphQL.js. It focuses on |
| 9 | +constructing types in JavaScript using the GraphQL.js type system, not the schema definition |
| 10 | +language (SDL). |
| 11 | + |
| 12 | +## What are abstract types? |
| 13 | + |
| 14 | +Most GraphQL types are concrete. They represent a specific kind of object, for example, a |
| 15 | +`Book` or an `Author`. Abstract types let a field return different types of objects depending |
| 16 | +on the data. |
| 17 | + |
| 18 | +This is useful when the return type can vary but comes from a known set. For example, a `search` |
| 19 | +field might return a book, an author, or a publisher. Abstract types let you model this kind of |
| 20 | +flexibility while preserving validation, introspection, and tool support. |
| 21 | + |
| 22 | +GraphQL provides two kinds of abstract types: |
| 23 | + |
| 24 | +- Interfaces define a set of fields that multiple object types must implement. |
| 25 | + - Use case: A `ContentItem` interface with fields like `id`, `title`, and `publishedAt`, |
| 26 | + implemented by types such as `Article` and `PodcastEpisode`. |
| 27 | +- Unions group together unrelated types that don't share any fields. |
| 28 | + - Use case: A `SearchResult` union that includes `Book`, `Author`, and `Publisher` types. |
| 29 | + |
| 30 | +## Defining interfaces |
| 31 | + |
| 32 | +To define an interface in GraphQL.js, use the `GraphQLInterfaceType` constructor. An interface |
| 33 | +must include a `name`, a `fields` function, and a `resolveType` function, which tells GraphQL which |
| 34 | +concrete type a given value corresponds to. |
| 35 | + |
| 36 | +The following example defines a `ContentItem` interface for a publishing platform: |
| 37 | + |
| 38 | +```js |
| 39 | +const { GraphQLInterfaceType, GraphQLString, GraphQLNonNull } = require('graphql'); |
| 40 | + |
| 41 | +const ContentItemInterface = new GraphQLInterfaceType({ |
| 42 | + name: 'ContentItem', |
| 43 | + fields: { |
| 44 | + id: { type: new GraphQLNonNull(GraphQLString) }, |
| 45 | + title: { type: GraphQLString }, |
| 46 | + publishedAt: { type: GraphQLString }, |
| 47 | + }, |
| 48 | + resolveType(value) { |
| 49 | + if (value.audioUrl) { |
| 50 | + return 'PodcastEpisode'; |
| 51 | + } |
| 52 | + if (value.bodyText) { |
| 53 | + return 'Article'; |
| 54 | + } |
| 55 | + return null; |
| 56 | + }, |
| 57 | +}); |
| 58 | +``` |
| 59 | + |
| 60 | +You can return either the type name as a string or the corresponding `GraphQLObjectType` instance. |
| 61 | +Returning the instance is recommended when possible for better type safety and tooling support. |
| 62 | + |
| 63 | +## Implementing interfaces with object types |
| 64 | + |
| 65 | +To implement an interface, define a `GraphQLObjectType` and include the interface in its |
| 66 | +`interfaces` array. The object type must implement all fields defined by the interface. |
| 67 | + |
| 68 | +The following example implements the `Article` and `PodcastEpisode` types that |
| 69 | +conform to the `ContentItem` interface: |
| 70 | + |
| 71 | +```js |
| 72 | +const { GraphQLObjectType, GraphQLString, GraphQLNonNull } = require('graphql'); |
| 73 | + |
| 74 | +const ArticleType = new GraphQLObjectType({ |
| 75 | + name: 'Article', |
| 76 | + interfaces: [ContentItemInterface], |
| 77 | + fields: { |
| 78 | + id: { type: new GraphQLNonNull(GraphQLString) }, |
| 79 | + title: { type: GraphQLString }, |
| 80 | + publishedAt: { type: GraphQLString }, |
| 81 | + bodyText: { type: GraphQLString }, |
| 82 | + }, |
| 83 | + isTypeOf: (value) => value.bodyText !== undefined, |
| 84 | +}); |
| 85 | + |
| 86 | +const PodcastEpisodeType = new GraphQLObjectType({ |
| 87 | + name: 'PodcastEpisode', |
| 88 | + interfaces: [ContentItemInterface], |
| 89 | + fields: { |
| 90 | + id: { type: new GraphQLNonNull(GraphQLString) }, |
| 91 | + title: { type: GraphQLString }, |
| 92 | + publishedAt: { type: GraphQLString }, |
| 93 | + audioUrl: { type: GraphQLString }, |
| 94 | + }, |
| 95 | + isTypeOf: (value) => value.audioUrl !== undefined, |
| 96 | +}); |
| 97 | +``` |
| 98 | + |
| 99 | +The `isTypeOf` function is optional. It provides a fallback when `resolveType` isn't defined, or |
| 100 | +when runtime values could match multiple types. If both `resolveType` and `isTypeOf` are defined, |
| 101 | +GraphQL uses `resolveType`. |
| 102 | + |
| 103 | +## Defining union types |
| 104 | + |
| 105 | +Use the `GraphQLUnionType` constructor to define a union. A union allows a field to return one |
| 106 | +of several object types that don't need to share fields. |
| 107 | + |
| 108 | +A union requires: |
| 109 | + |
| 110 | +- A `name` |
| 111 | +- A list of object types (`types`) |
| 112 | +- A `resolveType` function |
| 113 | + |
| 114 | +The following example defines a `SearchResult` union: |
| 115 | + |
| 116 | +```js |
| 117 | +const { GraphQLUnionType } = require('graphql'); |
| 118 | + |
| 119 | +const SearchResultType = new GraphQLUnionType({ |
| 120 | + name: 'SearchResult', |
| 121 | + types: [BookType, AuthorType, PublisherType], |
| 122 | + resolveType(value) { |
| 123 | + if (value.isbn) { |
| 124 | + return 'Book'; |
| 125 | + } |
| 126 | + if (value.bio) { |
| 127 | + return 'Author'; |
| 128 | + } |
| 129 | + if (value.catalogSize) { |
| 130 | + return 'Publisher'; |
| 131 | + } |
| 132 | + return null; |
| 133 | + }, |
| 134 | +}); |
| 135 | +``` |
| 136 | + |
| 137 | +Unlike interfaces, unions don’t declare any fields of their own. Clients use inline fragments |
| 138 | +to query fields from the concrete types. |
| 139 | + |
| 140 | +## Resolving abstract types at runtime |
| 141 | + |
| 142 | +GraphQL resolves abstract types dynamically during execution using the `resolveType` function. |
| 143 | + |
| 144 | +This function receives the following arguments: |
| 145 | + |
| 146 | +```js |
| 147 | +resolveType(value, context, info) |
| 148 | +``` |
| 149 | + |
| 150 | +It can return: |
| 151 | + |
| 152 | +- A `GraphQLObjectType` instance (recommended) |
| 153 | +- The name of a type as a string |
| 154 | +- A `Promise` resolving to either of the above |
| 155 | + |
| 156 | +If `resolveType` isn't defined, GraphQL falls back to checking each possible type's `isTypeOf` |
| 157 | +function. This fallback is less efficient and makes type resolution harder to debug. For most cases, |
| 158 | +explicitly defining `resolveType` is recommended. |
| 159 | + |
| 160 | +## Querying abstract types |
| 161 | + |
| 162 | +To query a field that returns an abstract type, use inline fragments to select fields from the |
| 163 | +possible concrete types. GraphQL evaluates each fragment based on the runtime type of the result. |
| 164 | + |
| 165 | +For example: |
| 166 | + |
| 167 | +```graphql |
| 168 | +{ |
| 169 | + search(term: "deep learning") { |
| 170 | + ... on Book { |
| 171 | + title |
| 172 | + isbn |
| 173 | + } |
| 174 | + ... on Author { |
| 175 | + name |
| 176 | + bio |
| 177 | + } |
| 178 | + ... on Publisher { |
| 179 | + name |
| 180 | + catalogSize |
| 181 | + } |
| 182 | + } |
| 183 | +} |
| 184 | +``` |
| 185 | + |
| 186 | +GraphQL's introspection system lists all possible types for each interface and union, which |
| 187 | +enables code generation and editor tooling to provide type-aware completions. |
| 188 | + |
| 189 | +## Best practices |
| 190 | + |
| 191 | +- Always implement `resolveType` for interfaces and unions to handle runtime type resolution. |
| 192 | +- Return the `GraphQLObjectType` instance when possible for better clarity and static analysis. |
| 193 | +- Keep `resolveType` logic simple, using consistent field shapes or tags to distinguish |
| 194 | +types. |
| 195 | +- Test `resolveType` logic carefully. Errors in `resolveType` can cause runtime errors that can |
| 196 | +be hard to trace. |
| 197 | +- Use interfaces when types share fields and unions when types are structurally unrelated. |
| 198 | + |
| 199 | +## Additional resources |
| 200 | + |
| 201 | +- [Constructing Types](https://www.graphql-js.org/docs/constructing-types/) |
| 202 | +- GraphQL Specification: |
| 203 | + - [Interfaces](https://spec.graphql.org/October2021/#sec-Interfaces) |
| 204 | + - [Unions](https://spec.graphql.org/October2021/#sec-Unions) |
0 commit comments