Each GraphQL service defines a set of types that describe the set of possible data that you can query on that service. This set of types is called a GraphQL schema. Then, when requests come in, they are checked and executed according to this schema.
Type description language
The types are described using the GraphQL schema language. It is similar to the GraphQL query language and allows you to describe schemas without being tied to any language.
Objects and their fields
An object is a special type that can contain fields.
type Character {
name: String!
appearsIn: [Episode!]!
}
Character
is a GraphQL object. It contains the fields name
and appearsIn
. String
is one of the built-in scalar types. These types cannot have subfields in a query. String!
means the field is non-nullable. If as a result of the request it turns out that this field is null
, then an error will be returned. [Episode!]!
is an array of Episode
objects. Since it does not allow null
values, you can always expect an array when you request the appearsIn
field (with zero or more elements, an empty array is not null
). Since Episode!
is also non-nullable, you can always expect every element in the array to be an Episode
object.
Arguments
Each field of an object can have arguments.
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
All arguments are named. Unlike JavaScript, where functions take a list of ordered arguments, all arguments in GraphQL are passed by name. In this example, the length
field has one unit
argument with a default METER
value. If the argument has a default value, it is optional.
The "!" can also be used to test the value of arguments for "null".
query DroidById($id: ID!) {
droid(id: $id) {
name
}
}
In this example, if the id
variable is set to null
, an error will be returned.
The Query and Mutation Types
There are two special types in the schema: Query
and Mutation
.
schema {
query: Query
mutation: Mutation
}
Every GraphQL service has a query type and can have a mutation type. These types are similar to regular object types, but they are special because they define an entry point for every GraphQL query, for example:
type Query {
hero(episode: Episode): Character
droid(id: ID!): Droid
}
The Query type describes all the query options that the client can execute. In the example, we indicate that the client can request fields for hero
(type Character
) or droid
(type Droid
). Mutations work in a similar way - you define fields for the Mutation
type, and they are available as root mutation fields that you can call in your query.
Scalar types
GraphQL objects have a name and fields, but at some point those fields must be converted to concrete data. This is where scalar types come in handy: they represent the leaves of the query. GraphQL contains several scalar types by default:
Int
: 32-bit signed integer.Float
: double-precision signed floating-point number.String
: a sequence of UTF‐8 characters.Boolean
: boolean valuetrue
orfalse
.ID
: unique identifier. Serializable in the same way asString
. Signals that the field value is not human readable.
You can define your scalar type with the scalar
keyword:
scalar Date
Then you must define how to serialize, deserialize, and validate this type. For example, you can specify that the Date
type should always be serialized to an integer timestamp, and your client should know that the type is in that format.
Enumerations
An enumeration is a special kind of scalar that is limited to a specific set of valid values. This allows you to check that any arguments of this type are one of the valid values.
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
In this example, the Episode
type is limited to three values: NEWHOPE
, EMPIRE
, and JEDI
.
Interfaces
An interface is an abstract type that includes a specific set of fields. For a type to implement an interface, it must include all of its fields. For example, you might have a Character
interface that represents any character in the Star Wars trilogy:
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
This means that any type that implements the Character
interface must contain all of its fields, with their arguments and return types. For example, here are some types that implement the Character
interface:
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
Both of these types contain all the fields from the Character
interface, but also include additional fields totalCredits
, starships
, and primaryFunction
that are specific to the character type.
Interfaces are useful when you want to return an object or collection of objects, but they can be of different types. The following request will throw an error:
Query:
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
primaryFunction
}
}
Variables:
{
"ep": "JEDI"
}
The hero
field returns a Character
type, which means it can be either Human
or Droid
, depending on the argument. In the above query, you can only specify fields that exist in the Character
interface, which does not include the Droid type specific primaryFunction
field.
To request a specific field for an object type, you need to use the inline fragment:
Query:
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
}
}
Variables:
{
"ep": "JEDI"
}
Unions
Unions are very similar to interfaces, but they do not specify common fields for types.
union SearchResult = Human | Droid | Starship
The members of the union type must be specific object types. You can't create a union type from interfaces or other unions.
If the request returns the SearchResult
type, then we can get Human
, Droid
, or Starship
. In this case, you need to use inline fragments to be able to query any fields:
{
search(text: "an") {
__typename
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}
The __typename
field is converted to a string that allows different types to be distinguished on the client. Additionally, since Human
and Droid
types share a common Character
interface, you can query their common fields in one place to avoid duplication:
{
search(text: "an") {
__typename
... on Character {
name
}
... on Human {
height
}
... on Droid {
primaryFunction
}
... on Starship {
name
length
}
}
}
Note that name
is still listed in Starship
, as it would not otherwise appear in the results as Starship
does not implement the Character
interface.
Also, by analogy with interfaces, we cannot make a request like this:
{
search(text: "an") {
__typename
name
... on Human {
height
}
... on Droid {
primaryFunction
}
... on Starship {
length
}
}
}
The error here is that the SearchResult
type does not contain a name
field. We can only get the result by using inline fragments for the types that are specified in the SearchResult
enumeration, or for the interfaces that these types implement.
Input type
GraphQL allows you to pass complex objects as arguments. This is especially useful in the case of mutations, where you want to pass an entire object to add to the database. The input type is defined in the same way as the query type, but using the input
keyword.
input ReviewInput {
stars: Int!
commentary: String
}
An example of using input type in mutations:
Query:
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
Variables:
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
The fields in the input object type can themselves refer to the input object types, but you cannot mix the input and output object types in your schema. Input object types also cannot have arguments in their fields.