GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data.

A GraphQL service is created by defining types and fields on those types, then providing functions for each field on each type.

1
2
3
4
5
6
7
8
type Query {
me: User
}

type User {
id: ID
name: String
}

Along with functions for each field on each type:

1
2
3
4
5
6
7
function Query_me(request) {
return request.auth.user;
}

function User_name(user) {
return user.getName()
}

Once a GraphQL service is running (typically at a URL on a web service), it can be sent GraphQL queries to validate and execute. A received query is first checked to ensure it only refers to the types and fields defined, then runs the provided functions to produce a result.

For example the query:

1
2
3
4
5
6
7
8
9
10
11
{
me {
name
}
}
// produce
{
"me": {
"name": "Luck"
}
}

Fields

GraphQL is about asking for specific fields on objects.

GraphQL queries can traverse related objects and their fields, letting clients fetch lots of related data in one request, instead of making several roundtrips as one would need in a classic RESTFul architecture.

Arguments

In GraphQL, every field and nested object can get its own set of arguments, making GraphQL a complete replacement for making multiple API fetches. You can even pass arguments into scalar fields, to implement data transformations once on the server, instead of on every separately.

1
2
3
4
5
6
{
human(id: '1000') {
name
height(uint: FOOT)
}
}

Arguments can be of many different types. GraphQL comes with a default set of types, but a GraphQL server can also declare its own custom types, as long as they can be serialized into your transport format.

Aliases

You can’t directly query for the same field with different arguments, so you need alias to rename the result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
// produce
{
"data": {
"empireHero": {
"name": "..."
},
"jediHero": {
"name": "..."
}
}
}

In the above example, the two hero fields would have conflicted, but since we can alias them to different names, we can get both results in one request.

Fragments

GraphQL includes reusable units called fragments. Fragments let you construct sets of fields, and then include them in queries where you need to.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}

fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}

Variables

When we start working with variables, we need to do three things:

  • Replace the static values in the query with $variableName

  • Declare $variableName as one of the variables accepted by the query

  • Pass variableName: value in the separate, transport-specific(usually JSON) variables dictionary

1
2
3
4
5
6
7
8
9
10
11
12
13
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}

// Variables
{
'episode': 'JEDI',
}

Now in our client code, we can simply pass a different variable rather than needing to construct an entirely new query.

Variable Definitions

The variable definitions are the part that looks like ($episode: Episode) in the query above. It works just like the argument definition for a function in a types language. It lists all of the variables, prefixed by $, followed by their type, in this case Episode.

All declared variables must be either scalars, enums, or input object types.

Variable Definition can be optional or required. In the case above, since there isn’t an ! next to the Episode type, it’s optional. But if the field you are passing the variable into requires a non-null argument, then the variable must be required.

Default Variables

Default variables can also be assigned to the variables in the query by adding the default value after the type declaration.

1
2
3
4
5
6
7
8
query HeroNameAndFriends($episode: Episode = 'JEDI') {
hero(episode: $episode) {
name
friends {
name
}
}
}

Directives

1
2
3
4
5
6
7
8
9
10
11
12
13
query Hero($episode: Episode, $withFriends: Boolean!) {
hero(episode: $episode) {
name
friends @include(if: $withFriends) {
name
}
}
}
// variables
{
"episode": "JEDI",
"withFriends": false,
}

We needed to use a new feature in GraphQL called directive. A directive can be attached to a field or fragment inclusion, and can affect execution of the query in any way the server desires. The core GraphQL specification includes exactly two directives, which must be supported by any spec-compliant GraphQL server implementation:

  • @include(if: Boolean): Only include this field in the result if the argument is true

  • @skip(if: Boolean): Skip this field if the argument is true

Mutations

If mutation field returns an object type, you can ask for nested fields. This can be useful for fetching the new state of an object after an update.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mutation CreateReviewForEpisode ($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $Review) {
stars
commentary
}
}
// Variables
{
"ep": "JEDI",
"review": {
"star": 5,
"commentary": "This is a great movie"
}
}

Here the createReview returns the star and commentary fields of the newly created review.

Multiple fields in mutations

A mutation can contain multiple fields, just like a query. There’s one important distinction between queries and mutations, other than the name.

While query fields are executed in parallel, mutation fields run in series, one after the other. This means that if we send two incrementCredits mutations in one request, the first is guaranteed to finished before the second begins, ensuring that we don’t end up with a race condition with ourselves.

Inline Fragments

GraphQL include the ability to define interfaces and union types.

If you are querying a field that returns an interface or a union type, you will need to use inline fragments to access data on the underlaying concrete type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
query HeroForEpisode ($ep: Episode!) {
hero (episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
// Variables
{
"ep": "JEDI"
}
// produce
{
"data": {
"hero": {
"name": "...",
"primaryFunction": "..."
}
}
}

In this query, the hero field returns the type Character, which might be either a Human or a Droid depending on the episode argument. In the direct selection, you can ask for fields that exists on the Character interface such as name.

To ask for a field on the concrete type, you need to use a inline fragment with a type condition. Because the first fragment is labeled as ... on Droid, the primaryFunction field will only be executed if the Character returned from hero is of the Droid type.

Named fragments can also be used in the same way, since a named fragment always has a type attached.

Meta fields

Given that there are some situations where you don’t know what type you will get back from the GraphQL service, you need some way to determine how to handle that data on the client. GraphQL allows you to request __typename, a meta field, at any point in a query to get the name of the object type at that point.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
search(text: "an") {
__typename
... on Human {
name
}
... on Droid {
name
}
... on Starship {
name
}
}
}
// produce
{
"data": {
"search": [
{
"__typename": "Human",
"name": "Han",
}, {
"__typename": "Human",
"name": "ELX",
}, {
"__typename": "Starship",
"name": "TIE"
}
]
}
}

In the above query, search returns a union type that can be one of three options. It would be impossible to tell apart the different types from the client without the __typename field.

Schemas and Types

Type System

GraphQL query language is basically about selecting fields on objects.

1
2
3
4
5
6
{
hero {
name
appearsIn
}
}
  1. We start with a special ‘root’ object

  2. We select the hero field on that

  3. For the object returned by hero, we select the name and appearsIn fields

Object Types and Fields

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:

1
2
3
4
type Character {
name: String!
appearsIn: [Episode]!
}
  • Character is a GraphQL Type, meaning it’s a type with some fields. Most of the types in your schema will be object types.

  • 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.

  • String is one of the built-in scalar types

  • 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.

  • [Episode]! represents an array of Episode objects. Since it is also non-nullable, you can always expect an array ( with zero or more items) when you query the appearsIn field.

Arguments

Every field on a GraphQL object type can have zero or more arguments, for example the length field below:

1
2
3
4
5
type Starship {
id: ID!
name: String!
length(uint: LengthUint = METER): Float
}

All arguments are named.

The Query and Mutation Types

Most types in schema will just be normal object types, but there are two types that are special within a schema:

1
2
3
4
schema {
query: Query
mutation: Mutation
}

Every GraphQL service has a query type and may not have a mutation type. These types are the same as a regular object type, but they are special because they define the entry point of every GraphQL query.

1
2
3
4
5
6
7
8
query {
hero {
name
}
droid(id: '2000') {
name
}
}

That means that the GraphQL service needs to have a Query type with hero and droid fields:

1
2
3
4
type Query {
hero(episode: Episode): Character
droid(id: ID!): Droid
}

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.

Scalar Types

  • Int: A signed 32-bit integer

  • Float: A signed double-precision floating-point value

  • String: A UTF-8 character sequence

  • Boolean: true or false

  • ID: The ID Scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID types serialized in the same way as a String; However, defining it as an ID signifies that it is not intended to be human-readable.

In most GraphQL service implementation, there is also a way to specify custom scalar types.

1
scalar Date

Enumeration Types

Also called Enums, enumeration types are a special kind of scalar that is restricted to a particular set of allowed values.

1
2
3
4
5
enum Episode {
NEWHOPE
EMPIRE
JEDI
}

This means that wherever we use the type Episode in our schema, we expect it to be exactly one of NEWHOPE, EMPIRE, JEDI.

Lists and Non-Null

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 modifier that affects validation of those values.

Interfaces

An Interface is an abstract type that includes a certain set of fields that a type must include to implement the interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// define
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}

// implement
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]
appearsId: [Episode]!
primaryFunction: String
}

Union Types

Union Types are similar to interfaces, but they don’t get to specify any common fields between the types.

1
union SearchResult = Human | Droid | Starship

Input Types

In GraphQL schema language, input types look exactly the same as regular object types, but with the keyword input instead of type:

1
2
3
4
input ReviewInput {
stars: Int!
commentary: String
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
// Variables

{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "Commentary"
}
}