Skip to main content

GraphQL

http://graphql.org/

Queries

Basic query

{ status }

{ status: 'available' }

Nesting

{ hero { name height } }

{ hero:
{ name: "Luke Skywalker",
height: 1.74 } }

Lists

{ friends { name } }

{ friends:
[ { name: "Luke Skywalker" },
{ name: "Han Solo" },
{ name: "R2D2" } ] }

GraphQL queries look the same for both single items or lists of items.

Lookups

{
hero(id: "1000") { id name }
}

{ hero:
{ id: "1000",
{ name: "Luke Skywalker" } }

Aliases

{
luke: hero(id: "1000") { name }
han: hero(id: "1001") { name }
}

{ luke:
{ name: "Luke Skywalker" },
han:
{ name: "Han Solo" } }

Operation names and variables

Query

query FindHero($id: String!) {
hero(id: $id) { name }
}

Just to make things less ambiguous. Also, to use variables, you need an operation name.

Variables

{ id: '1000' }

Mutations

Query

{ createReview($review) { id } }

Variables

{ review: { stars: 5 } }

{ createReview: { id: 5291 } }

Mutations are just fields that do something when queried.

Multiple types

{
search(q: "john") {
id
... on User { name }
... on Comment { body author { name } }
}
}

Great for searching.

Over HTTP

GET

fetch('http://myapi/graphql?query={ me { name } }')

POST

fetch('http://myapi/graphql', {
body: JSON.stringify({
query: '...',
operationName: '...',
variables: { ... }
})
})

Schema

Basic schemas

type Query {
me: User
users(limit: Int): [User]
}

type User {
id: ID!
name: String
}

See: sogko/graphql-shorthand-notation-cheat-sheet

Built in types

Scalar types

| Int | Integer | | Float | Float | | String | String | | Boolean | Boolean | | ID | ID |

Type definitions

| scalar | Scalar type | | type | Object type | | interface | Interface type | | union | Union type | | enum | Enumerable type | | input | Input object type |

Type modifiers

| String | Nullable string | | String! | Required string | | [String] | List of strings | | [String]! | Required list of strings | | [String!]! | Required list of required strings |

Mutations

type Mutation {
users(params: ListUsersInput) [User]!
}

Interfaces

interface Entity {
id: ID!
}

type User implements Entity {
id: ID!
name: String
}

Enums

enum DIRECTION {
LEFT
RIGHT
}

type Root {
direction: DIRECTION!
}

Unions

type Artist { ··· }
type Album { ··· }

union Result = Artist | Album

type Query {
search(q: String) [Result]
}

References

Essential quotes

  • "GraphQL is about asking for specific fields on objects"
  • "The query has exactly the same shape as the result"
  • "Clients can fetch lots of related data in one request, instead of making several roundtrips in a classic REST architecture"
  • "GraphQL queries look the same for both single items or lists of items"
  • "In REST, you can only pass a single set of arguments but in GraphQL, every field and nested object can get its own set of arguments, making GraphQL a complete replacement for making multiple API fetches"
  • "While query fields are executed in parallel, mutation fields run in series, one after the other"

Glossary

  • Type
  • Field
  • Function for each field on each type
type Query {
me: User
}

type User {
id: ID
name: String
}

function Query_me(request) {
return request.auth.user;
}

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

Steps involved

  1. Receive GraphQL query
  2. Validate query against the types and field that are defined
  3. Run provided functions to produce the result

Example Query

{
me {
name
}
}

JSON result

{
"me": {
"name": "Luke Skywalker"
}
}

Example Query

{
hero {
name
}
}

JSON result

{
"data": {
"hero": {
"name": "R2-D2"
}
}
}

Nested data with arrays

Query

{
hero {
name
# Queries can have comments!
friends {
name
}
}
}

JSON result

{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}

Pass arguments to fields

Query

{
human(id: "1000") {
name
height
}
}

JSON result

{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 1.72
}
}
}

Pass arguments into scalar fields to let the server transform it

Query

{
human(id: "1000") {
name
height(unit: FOOT) # Enumeration type
}
}

JSON result

{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 5.6430448
}
}
}

Data types for arguments

  • Enumeration type
  • GraphQL's default set of types
  • The GraphQL server can declare its own custom types (must be serializable)

Aliases to query the same field with different arguments

Aliases let you rename the result of a field to anything you want

{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}

JSON result

{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}

Fragments to split queries into reusable units

{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}

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

JSON result

{
"data": {
"leftComparison": {
"name": "Luke Skywalker",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
},
{
"name": "C-3PO"
},
{
"name": "R2-D2"
}
]
},
"rightComparison": {
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}

Use variables in fragments

query HeroComparison($first: Int = 3) {
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}

fragment comparisonFields on Character {
name
friendsConnection(first: $first) {
totalCount
edges {
node {
name
}
}
}
}

Variable

{"first": 2}

JSON result

{
"data": {
"leftComparison": {
"name": "Luke Skywalker",
"friendsConnection": {
"totalCount": 4,
"edges": [
{
"node": {
"name": "Han Solo"
}
},
{
"node": {
"name": "Leia Organa"
}
}
]
}
},
"rightComparison": {
"name": "R2-D2",
"friendsConnection": {
"totalCount": 3,
"edges": [
{
"node": {
"name": "Luke Skywalker"
}
},
{
"node": {
"name": "Han Solo"
}
}
]
}
}
}
}

Operation types

  • query (can be omitted with "query shorthand syntax" -> no name + no variable definitions)
  • mutation
  • subscription

Operation name

  • Is only required in multi-operation documents
  • "Name your queries to make the code less ambigous"
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}

Using variables

  • It's not needed to manipulate the query string at runtime
  • Never do string interpolation to construct queries from user-supplied values

Steps for using variables

  1. Replace the static value with $variableName
  2. Declare $variableName as an accepted variable
  3. Pass variableName: value (usually) as JSON

Query with variable definition

query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}

Variable

{
"episode": "JEDI"
}

JSON result

{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}

Variable definitions

  • Like arguments to a query
  • Must be prefixed with a $ sign followed by their type ($episode: Episode)

Required variable definitions

  • are suffixed with a ! sign ($episode: Episode!)

Default variable values

  • are suffixed with " sign ($episode: Episode = JEDI)
query HeroNameAndFriends($episode: Episode = JEDI) {
hero(episode: $episode) {
name
friends {
name
}
}
}

Directives to dynamically change the structure using variables

  • Example: UI component that has a summarized and a detailed view
  • Directives can be attached to a filed or fragment incusion
  • Two directives built into the spec:
    • include(if: Boolean) - Includes this field only if the argument is true
    • skip(if: Boolean) - Skips this field if the argument is true
query Hero($episode: Episode, $withFriends: Boolean!) {
hero(episode: $episode) {
name
friends @include(if: $withFriends) {
name
}
}
}

Summarized view

Variables

{
"episode": "JEDI",
"withFriends": false
}

JSON result

{
"data": {
"hero": {
"name": "R2-D2"
}
}
}

Detailed view

Variables

{
"episode": "JEDI",
"withFriends": true
}

JSON result

{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}

Mutations

  • We can mutate and query the new value of a field with one request
  • createReview field returns the stars and commentary fields of the newly created review
  • The review variable is not a scalar - It's an input object type
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!"
}
}

JSON result

{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}

Multiple fields in mutations

  • While query fields are executed in parallel, mutation fields run in series, one after the other
  • 2 incrementCredits mutations in one request mean: the first is guaranteed to finish before the second begins

Inline Fragments for fields that return an interface or a union type

  • The hero field returns the type Character, which might be either a Human or a Droid depending on the episode argument
  • name exists on both so you can directly ask for that in the query
  • To ask for a field on the concrete type, you need to use an inline fragment with a type condition
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}

Variables

{
"ep": "JEDI"
}

JSON result

{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}