|
| 1 | +--- |
| 2 | +title: Schemas and Types |
| 3 | +layout: ../_core/DocsLayout |
| 4 | +category: Learn |
| 5 | +permalink: /learn/schema/ |
| 6 | +next: /docs/schema/ |
| 7 | +--- |
| 8 | + |
| 9 | +### ToC |
| 10 | + |
| 11 | +* Type system |
| 12 | +* Type language |
| 13 | +* Basics (Schema, Objects & Fields) |
| 14 | +* Arguments (TODO) |
| 15 | +* Scalars & Enums |
| 16 | +* Lists & NonNull (mention error handling) |
| 17 | +* Interfaces & Unions |
| 18 | + |
| 19 | +### Type system |
| 20 | + |
| 21 | +If you've seen a GraphQL query before, you know that the GraphQL query language is basically about selecting fields on objects. So, for example, in the following query: |
| 22 | + |
| 23 | +```graphql |
| 24 | +{ |
| 25 | + hero { |
| 26 | + name |
| 27 | + appearsIn |
| 28 | + } |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +1. We start with the a special "root" object |
| 33 | +2. We select the `hero` field on that |
| 34 | +3. For each object returned by `hero`, we select the `name` and `appearsIn` fields |
| 35 | + |
| 36 | +So you can predict what the query will return without knowing that much about the server. But the question remains - what fields can we select? What kinds of objects might they return? What fields are available on those sub-objects? That's where the schema comes in. |
| 37 | + |
| 38 | +Every GraphQL service defines a set of types which completely describe the set of possible data you can query on that service. Then, when queries come in, they are validated and executed against that schema. |
| 39 | + |
| 40 | +### Type language |
| 41 | + |
| 42 | +GraphQL services can be written in any language. Since we can't rely on a specific programming language syntax, like JavaScript, to talk about GraphQL schemas, we'll define our own simple language. We'll use the "GraphQL schema language" - it's similar to the query language, and allows us to talk about GraphQL schemas in a language-agnostic way. |
| 43 | + |
| 44 | +### Objects and fields |
| 45 | + |
| 46 | +The most basic components of a GraphQL schema are object types, which just represent a kind of object you can fetch from your service, and what fields it has. In the GraphQL schema language, we might represent it like this: |
| 47 | + |
| 48 | +```graphql |
| 49 | +type Character { |
| 50 | + name: String! |
| 51 | + appearsIn: [Episode] |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +The language is pretty readable, but let's go over it so that we can have a shared vocabulary: |
| 56 | + |
| 57 | +- `Character` is a _GraphQL Object Type_, meaning it's a type with some fields. Most of the types in your schema will be object types. |
| 58 | +- `name` and `appearsIn` are _fields_ on the `Character` type. That means that `name` and `appearsIn` are the only fields that can appear in any part of a GraphQL query that operates on the `Character` type. |
| 59 | +- `String` is one of the built-in _scalar_ types - these are types that resolve to a single scalar object, and can't have sub-selections in the query. We'll go over scalar types more later. |
| 60 | +- `String!` means that the field is _non-nullable_, meaning that the GraphQL service promises to always give you a value when you query this field. In the type language, we'll represent those with an exclamation mark. |
| 61 | +- `[Episode]` represents an _array_ of `Episode` objects. This means that you can always expect an array, with zero or more items, when you query the `appearsIn` field. |
| 62 | + |
| 63 | +Now you know what a GraphQL object type looks like, and how to read the basics of the GraphQL type language. |
| 64 | + |
| 65 | +### The Query and Mutation types |
| 66 | + |
| 67 | +Most types in your schema will just be normal object types, but there are two types that are unique within a schema: |
| 68 | + |
| 69 | +```graphql |
| 70 | +schema { |
| 71 | + query: Query |
| 72 | + mutation: Mutation |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +Every GraphQL service has exactly zero or one each of the `Query` and `Mutation` types. These types are mostly the same as a regular object type, but they are special because they define the _entry point_ of every GraphQL query. So if you see a query that looks like: |
| 77 | + |
| 78 | +```graphql |
| 79 | +query { |
| 80 | + hero { |
| 81 | + name |
| 82 | + } |
| 83 | + droid(id: "2001") { |
| 84 | + name |
| 85 | + } |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +That means that the GraphQL service needs to have a `Query` type with `hero` and `droid` fields: |
| 90 | + |
| 91 | +```graphql |
| 92 | +type Query { |
| 93 | + hero(episode: Episode): Character |
| 94 | + droid(id: ID!): Droid |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +Mutations work in a similar way - you define fields on the `Mutation` type, and those are available as the root mutation fields you can call in your query. |
| 99 | + |
| 100 | +It's important to remember that other than this special status, the `Query` and `Mutation` types are the same as any other GraphQL object type, and their fields work exactly the same way. |
| 101 | + |
| 102 | +### Scalar types |
| 103 | + |
| 104 | +A GraphQL object type has a name and fields, but at some point those fields have to resolve to some concrete data. That's where the scalar types come in: they represent the leaves of the query. |
| 105 | + |
| 106 | +In the following query, the `name` and `appearsIn` will resolve to scalar types: |
| 107 | + |
| 108 | +```graphql |
| 109 | +{ |
| 110 | + hero { |
| 111 | + name |
| 112 | + appearsIn |
| 113 | + } |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +We know this because those fields don't have any sub-fields - they are the leaves of the query. |
| 118 | + |
| 119 | +GraphQL comes with a set of default scalar types out of the box: |
| 120 | + |
| 121 | +- `Int`: A signed 32‐bit integer. |
| 122 | +- `Float`: A signed double-precision floating-point value. |
| 123 | +- `String`: A UTF‐8 character sequence. |
| 124 | +- `Boolean`: `true` or `false`. |
| 125 | +- `ID`: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an `ID` signifies that it is not intended to be human‐readable. |
| 126 | + |
| 127 | +In most GraphQL service implementations, there is also a way to specify custom scalar types. For example, we could define a `Date` type: |
| 128 | + |
| 129 | +```graphql |
| 130 | +scalar Date |
| 131 | +``` |
| 132 | + |
| 133 | +Then it's up to our implementation to define how that type should be serialized, deserialized, and validated. For example, you could specify that the `Date` type should always be serialized into an integer timestamp, and your client should know to expect that format for any date fields. |
| 134 | + |
| 135 | +### Enumeration types |
| 136 | + |
| 137 | +Also called _Enums_, enumeration types are a special kind of scalar that is restricted to a particular set of allowed values. This allows you to: |
| 138 | + |
| 139 | +1. Validate that any arguments of this type are one of the allowed values |
| 140 | +2. Communicate through the type system that a field will always be one of a finite set of values |
| 141 | + |
| 142 | +Here's what an enum definition might look like in the GraphQL schema language: |
| 143 | + |
| 144 | +```graphql |
| 145 | +enum Episode { |
| 146 | + NEWHOPE |
| 147 | + EMPIRE |
| 148 | + JEDI |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +This means that wherever we use the type `Episode` in our schema, we expect it to be exactly one of `NEWHOPE`, `EMPIRE`, or `JEDI`. |
| 153 | + |
| 154 | +Note that GraphQL service implementations in various languages will have their own language-specific way to deal with enums. In languages that support enums as a first-class citizen, the implementation might take advantage of that; in a language like JavaScript with no enum support, these values might be internally mapped to a set of integers. However, this should not leak out to the client, which will operate entirely in terms of the string names of the values. |
| 155 | + |
| 156 | +### Lists and Non-Null |
| 157 | + |
| 158 | +Object types, scalars, and enums are the only kinds of types you can define in GraphQL. But when you use the types in other parts of the schema, or in your query variable declarations, you can apply additional _type modifiers_ that affect validation of those values. Let's look at an example: |
| 159 | + |
| 160 | +```graphql |
| 161 | +type Character { |
| 162 | + name: String! |
| 163 | + appearsIn: [Episode] |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +Here, we're using a `String` type and marking it as _Non-Null_ by adding an exclamation mark, `!` after the type name. This means that our server always expects to return a non-null value for this field, and if it ends up getting a null value that will actually trigger a GraphQL execution error, letting the client know that something has gone wrong. |
| 168 | + |
| 169 | +The Non-Null type modifier can also be used when defining arguments for a field, which will cause the GraphQL server to return a validation error if a null value is passed as that argument, whether in the GraphQL string or in the variables. |
| 170 | + |
| 171 | +Lists work in a similar way: We can use a type modifier to mark a type as a `List`, which indicates that this field will return an array of that type. In the schema language, this is denoted by wrapping the type in square brackets, `[` and `]`. It works the same for arguments, where the validation step will expect an array for that value. |
| 172 | + |
| 173 | +The Non-Null and List modifiers can be combined. For example, you can have a List of Non-Null Strings: |
| 174 | + |
| 175 | +```graphql |
| 176 | +myField: [String!] |
| 177 | +``` |
| 178 | + |
| 179 | +This means that the _list itself_ can be null, but it can't have any null members. For example, in JSON: |
| 180 | + |
| 181 | +```js |
| 182 | +myField: null // valid |
| 183 | +myField: ['a', 'b'] // valid |
| 184 | +myField: ['a', null, 'b'] // error |
| 185 | +``` |
| 186 | + |
| 187 | +Now, let's say we defined a Non-Null List of Strings: |
| 188 | + |
| 189 | +```graphql |
| 190 | +myField: [String]! |
| 191 | +``` |
| 192 | + |
| 193 | +This means that the list itself cannot be null, but it can contain null values: |
| 194 | + |
| 195 | +```js |
| 196 | +myField: null // error |
| 197 | +myField: ['a', 'b'] // valid |
| 198 | +myField: ['a', null, 'b'] // valid |
| 199 | +``` |
| 200 | + |
| 201 | +You can arbitrarily nest any number of Non-Null and List modifiers, according to your needs. |
| 202 | + |
| 203 | +### Interfaces |
| 204 | + |
| 205 | +Like many type systems, GraphQL supports interfaces. An _Interface_ is an abstract type that includes a certain set of fields that a type must include to implement the interface. |
| 206 | + |
| 207 | +For example, you could have an interface `Character` that represents any character in the Star Wars trilogy: |
| 208 | + |
| 209 | +```graphql |
| 210 | +interface Character { |
| 211 | + id: String! |
| 212 | + name: String |
| 213 | + friends: [Character] |
| 214 | + appearsIn: [Episode] |
| 215 | +} |
| 216 | +``` |
| 217 | + |
| 218 | +This means that any type that _implements_ `Character` needs to have these exact fields, with these arguments and return types. |
| 219 | + |
| 220 | +For example, here are some types that might implement `Character`: |
| 221 | + |
| 222 | +```graphql |
| 223 | +type Human : Character { |
| 224 | + id: String! |
| 225 | + name: String |
| 226 | + friends: [Character] |
| 227 | + appearsIn: [Episode] |
| 228 | + homePlanet: String |
| 229 | +} |
| 230 | + |
| 231 | +type Droid : Character { |
| 232 | + id: String! |
| 233 | + name: String |
| 234 | + friends: [Character] |
| 235 | + appearsIn: [Episode] |
| 236 | + primaryFunction: String |
| 237 | +} |
| 238 | +``` |
| 239 | + |
| 240 | +You can see that both of these types have all of the fields from the `Character` interface, but also bring in extra fields, `homePlanet` and `primaryFunction`, that are specific to that particular type of character. |
| 241 | + |
| 242 | +Interfaces are useful when you want to return an object or set of objects, but those might be of several different types. For example, in the following query: |
| 243 | + |
| 244 | +```graphql |
| 245 | +query HeroForEpisode($ep: Episode!){ |
| 246 | + hero(episode: $ep) { |
| 247 | + name |
| 248 | + } |
| 249 | +} |
| 250 | +``` |
| 251 | + |
| 252 | +The `hero` field returns the type `Character`, which means it might be either a `Human` or a `Droid` depending on the `episode` argument. In the query above, you can only ask for fields that exist on the `Character` interface, and to ask for a field on the concrete type, you need to use a fragment: |
| 253 | + |
| 254 | +```graphql |
| 255 | +query HeroForEpisode($ep: Episode!){ |
| 256 | + hero(episode: $ep) { |
| 257 | + name |
| 258 | + ... on Droid { |
| 259 | + primaryFunction |
| 260 | + } |
| 261 | + } |
| 262 | +} |
| 263 | +``` |
| 264 | + |
| 265 | +Learn more about this in the [conditional fragments](XXX) section in the query guide. |
| 266 | + |
| 267 | +### Union types |
| 268 | + |
| 269 | +Union types are very similar to interfaces, but they don't get to specify any common fields between the types. |
| 270 | + |
| 271 | +XXX no example in SWAPI |
| 272 | + |
| 273 | +```graphql |
| 274 | +union SearchResult = Photo | Person |
| 275 | + |
| 276 | +type Person { |
| 277 | + name: String |
| 278 | + age: Int |
| 279 | +} |
| 280 | + |
| 281 | +type Photo { |
| 282 | + height: Int |
| 283 | + width: Int |
| 284 | +} |
| 285 | +``` |
| 286 | + |
| 287 | +In this case, if you query a field that returns the `SearchResult` union type, you need to use a conditional fragment to be able to query any fields at all. |
0 commit comments