Introduction to  the dark force of

Created by Facebook in 2012

 

Open sourced in 2015

 

 

GraphQL is a query language for your API

 

GraphQL is a PATTERN not a technology

 

 

Created by FB in 2012

 

Open sourced in 2015

 

 

GraphQL is a query language for your API

 

GraphQL is a PATTERN not a technology

 

GraphQL doesn't store data

 

GraphQL is a replacement for REST

GraphQL is a replacement of REST

Instarwarsgram

JSON injected in the UI

{
  "data": {
    "person": {
      "name": "Darth Vader",
      "birthYear": "41.9BBY",
      "planet": {
        "name": "Tatooine"
      },
      "movies": [
        { "title": "A New Hope" },
        { "title": "The Empire Strikes Back" },
        { "title": "Return of the Jedi" },
        { "title": "Revenge of the Sith" }
      ]
    }
  }
}

Person

{
  "data": {
    "person": {
      "name": "Darth Vader",
      "birthYear": "41.9BBY",
      "planetId": 53,
      "movieIds": [1, 2, 3, 6],
      "saberColor"..
    }
  }
}
GET - /person/42

Then.. Planets & Movies

{
  "data": {
    "person": {
      "name": "Darth Vader",
      "birthYear": "41.9BBY",
      "planetId": 53,
      "movieIds": [1, 2, 3, 6],
      "saberColor"...
    },
  }
}
GET - /person/42
    GET - /planets/53
    GET - /movies/1
    GET - /movies/2
    GET - /movies/3
    GET - /movies/6
{
  "data": {
    "planet": {
      "name": "Tatooine",
      "positionX": "234.323",
      "positionY...
     },
  }
}
{
  "data": {
    "movie": {
      "title": "A New Hope",
     },
  }
}
{
  "data": {
    "movie": {
      "title": "A New Hope",
     },
  }
}
{
  "data": {
    "movie": {
      "title": "A New Hope",
     },
  }
}
{
  "data": {
    "movie": {
      "id": 6
      "title": "A New Hope",
      "date": "19/10/1983",
      "personIds"...
     },
  }
}

Result

GET - /person/42
    GET - /planets/53
    GET - /movies/1
    GET - /movies/2
    GET - /movies/3
    GET - /movies/6
{
  "data": {
    "person": {
      "name": "Darth Vader",
      "birthYear": "41.9BBY",
      "planet": {
        "name": "Tatooine"
      },
      "movies": [
        { "title": "A New Hope" },
        { "title": "The Empire Strikes Back" },
        { "title": "Return of the Jedi" },
        { "title": "Revenge of the Sith" }
      ]
    }
  }
}

other implementation

{
  "data": {
    "person": {
      "name": "Darth Vader",
      "birthYear": "41.9BBY",
      "planet": {
        "name": "Tatooine"
      },
      "movies": [
        { "title": "A New Hope" },
        { "title": "The Empire Strikes Back" },
        { "title": "Return of the Jedi" },
        { "title": "Revenge of the Sith" }
      ]
    }
  }
}
GET - /person/42
GET - /person/42/planets
GET - /person/42/movies

Over-fetching

{
  "data": {
    "person": {
      "firstName": "Anakin",
      "lastName": "Skywalker",
      "name": "Darth Vader",
      "birthYear": "41.9BBY",
      "favoriteColor": "black",
      "power": 3000,
      "saberColor": "red",
      "status": "single"
    }
  }
}

REST API always return a fixed structure.

Most of the time you don't need all the information.

Think mobile, and bad network

Under-fetching

GET - /person/42
GET - /person/42/planets
GET - /person/42/movies

 

We need multiple calls for one view.

Introducing BFF

Backend for Frontend

Back for iOs

Back for Web

Movie

Planet

Person

Monolith

Movie

Planet

Person

Router

BFF method

{
  "data": {
    "person": {
      "name": "Darth Vader",
      "birthYear": "41.9BBY",
      "planet": {
        "name": "Tatooine"
      },
      "movies": [
        { "title": "A New Hope" },
        { "title": "The Empire Strikes Back" },
        { "title": "Return of the Jedi" },
        { "title": "Revenge of the Sith" }
      ]
    }
  }
}
GET - /person/presentation/42

So isn't that good?

So isn't that good?

Too many endpoints

New features = new endpoints

Hard to maintain

One update = multiple changes

Complex language

Put VS Post VS Patch ?

Query / Params / Body / Headers ?

 

Poor documentation

Poor validation

Input & Output

Versionning

Now introducing GraphQL

GraphQL is a query language for your API

GraphQL provides

a description of the data in your API

GraphQL is WYWIWYG

* What you want is what you get
query {

}
query {
   person(id: 42)
}
query {
   person(id: 42) {
     name
     birthYear
   }
}
query {
   person(id: 42) {
     name
     birthYear
     planet {
       name
     }
     movies {
       title
     }
   }
}
query {
   person(id: 42) {
     name
     birthYear
     planet {
       name
     }
     movies {
       title
     }
   }
}
{
  "person": {
    "name": "Darth Vader",
    "birthYear": "41.9BBY",
    "planet": {
      "name": "Tatooine"
    },
    "movies": [
      { "title": "A New Hope" },
      { "title": "The Empire Strikes Back" },
      { "title": "Return of the Jedi" },
      { "title": "Revenge of the Sith" }
    ]
  }
}
query {
   person(id: 42) {
     name
     birthYear
     saberColor
     planet {
       name
     }
     movies {
       title
     }
   }
}
{
  "person": {
    "name": "Darth Vader",
    "birthYear": "41.9BBY",
    "saberColor": "red",
    "planet": {
      "name": "Tatooine"
    },
    "movies": [
      { "title": "A New Hope" },
      { "title": "The Empire Strikes Back" },
      { "title": "Return of the Jedi" },
      { "title": "Revenge of the Sith" }
    ]
  }
}
{
  mutation {
     createPlanet(name: "Padoïne") {
       id 
       name
       date
     }
  }
}
{
  mutation {
     createPlanet(name: "Padoïne") {
       id 
       name
       date
       habitant {
         name
       }
     }
  }
}

How does this magic work??

Step 1 - schema.graphql / types definitions

type Person {
  id: ID!
  name: String!
  birthYear: Int!
  picture: String
}
type Person {
  id: ID!
  name: String!
  birthYear: Int!
  picture: String!
  planet: Planet!
  movies: [Movies]
}
type Planet {
  id: ID!
  name: String!
  positionX: Int!
  positionY: Int!
}
scalar DateTime

type Movie {
  id: ID!
  title: String!
  date: DateTime
  actors: [Person]
}
type Person {
  id: ID!
  name: String!
  birthYear: Int!
  picture: String!
  planet: Planet!
  movies: [Movies]
  saberColor: SaberColor
}

enum SaberColor {
  Red
  Blue
  Green
  Purple
  Pink
}
type Planet {
  id: ID!
  name: String!
  positionX: Int!
  positionY: Int!
}
type AirWings implements Vehicule {
  maxSpeed: Int
  droid: Droid
  pilote: Person
}
interface Vehicle {
  maxSpeed: Int
}
scalar DateTime

type Movie {
  id: ID!
  title: String!
  date: DateTime
  actors: Person[]
}
type FalconMilenium implements Vehicule {
    maxSpeed: Int
    pilote1: Person
    pilote2: Person
}
type Query {
  person(id: ID!): Person
  planets: [Planets]
  movies(size: Int, orderBy: String): [Movies]
}
type Query {
  person(id: ID!): Person
  planets: [Planets]
  movies(size: Int, orderBy: String): [Movies]
}

input PersonInput {
  name: String
}

type Mutation {
  createPlanet(name: Planet!): Planet!
  createPerson(person: PersonInput!): Person!
}
type Person {
  id: ID!
  birthYear: Int!
  name: String!
  planet: Planet!
  movies: [Movies]
}

type Planet {
  id: ID!
  name: String!
  positionX: Int!
  positionY: Int!
}

type Movie {
  id: ID!
  title: String!
  date: DateTime
  actors: [Person]
}

type Query {
  person(id: ID!): Person
  planets: [Planets]
  movies(size: Int, orderBy: String): [Movies]
}

typeDefs

2. Resolvers

type Person {
  id: ID!
  birthYear: Int!
  name: String!
  planet: Planet!
  movies: [Movies]
}

type Planet {
  id: ID!
  name: String!
  positionX: Int!
  positionY: Int!
}

type Movie {
  id: ID!
  title: String!
  date: DateTime
  actors: [Person]
}

type Query {
  person(id: ID!): Person
  planets: [Planets]
  movies(size: Int, orderBy: String): [Movies]
}

resolvers

const resolver = (parent, args, ctx, infos) =>
const resolvers = {
  Query: {
    person: (parent, {id}, ctx, infos) => ctx.db.person.findById(id),
    planets: (parent, args, ctx, infos) => ctx.db.planets.findMany(),
    movies: (parent, {size, orderBy}, ctx, infos) => ctx.db.movies.findMany({size,orderBy}),
  },
  Person: {    
    planet: ({planet_id}, {id}, ctx, infos) => ctx.db.planet.findById(planet_id),
    movies: ({movie_ids}, {id}, ctx, infos) => ctx.db.movie.findByIds(movie_ids),
  },
  /*
  Planet: { ..},
  Movie: {.. },
  */
};

resolvers

const resolver = (parent, args, ctx, infos) =>
const resolvers = {
  Query: {
    person: (parent, { id }, ctx, infos) => ctx.db.person.findById(id),
    planets: (parent, args, ctx, infos) => ctx.db.planets.findMany(),
    movies: (parent, { size, orderBy}, ctx, infos) => {
      return ctx.db.movies.findMany({ size, orderBy })
    },
  },
  Person: {    
    id: (parent) => parent.id, // Default Resolver
    name: (parent) => parent.name, // Default Resolver
    birthYear: (parent) => root.birthYear, // Default Resolver
    planet: ({ planet_id }, { id }, ctx, infos) => ctx.db.planet.findById(planet_id),
    movies: ({ movie_ids }, { id }, ctx, infos) => ctx.db.movie.findByIds(movie_ids),
  },
  /*
  Planet: { ..},
  Movie: {.. },
  */
};

resolvers

const resolvers = {
  Query: {
    person: (parent, { id }, ctx, infos) => ctx.db.person.findById(id),
    planets: (parent, args, ctx, infos) => ctx.db.planets.findMany(),
    movies: (parent, { size, orderBy}, ctx, infos) => {
      return ctx.db.movies.findMany({ size, orderBy })
    },
  },
  Person: {    
    id: (parent) => parent.id,
    name: (parent) => parent.name,
    birthYear: (parent) => parent.birthYear,
    planet: ({ planet_id }, { id }, ctx, infos) => ctx.db.planet.findById(planet_id),
    movies: ({ movie_ids }, { id }, ctx, infos) => ctx.db.movie.findByIds(movie_ids),
  },
  /*
  Planet: { ..},
  Movie: {.. },
  */
};

resolvers

query {
   person(id: 42) {
     name
     birthYear
     planet {
       name
     }
     movies {
       title
     }
   }
}
const resolvers = {
  Query: {
    person: (parent, { id }, ctx, infos) => ctx.db.person.findById(id),
    planets: (parent, args, ctx, infos) => ctx.db.planets.findMany(),
    movies: (parent, { size, orderBy}, ctx, infos) => ctx.db.movies.findMany({ size, orderBy }),
  },
  Person: {    
    id: (parent) => parent.id,
    name: (parent) => parent.name,
    birthYear: (parent) => parent.birthYear,
    planet: ({ planet_id }, { id }, ctx, infos) => ctx.db.planet.findById(planet_id),
    movies: ({ movie_ids }, { id }, ctx, infos) => ctx.db.movie.findByIds(movie_ids),
  },
  /*
  Planet: { ..},
  Movie: {.. },
  */
};

Query

query {
   person(id: 42) {
     name
     birthYear
     planet {
       name
     }
     movies {
       title
     }
   }
}
const resolvers = {
  Query: {
    person: (parent, { id }, ctx, infos) => ctx.db.person.findById(id),
    planets: (root, args, ctx, infos) => ctx.db.planets.findMany(),
    movies: (root, { size, orderBy}, ctx, infos) => ctx.db.movies.findMany({ size, orderBy }),
  },
  Person: {    
    id: (parent) => parent.id,
    name: (parent) => parent.name,
    birthYear: (parent) => root.birthYear,
    planet: ({ planet_id }, { id }, ctx, infos) => ctx.db.planet.findById(planet_id),
    movies: ({ movie_ids }, { id }, ctx, infos) => ctx.db.movie.findByIds(movie_ids),
  },
  /*
  Planet: { ..},
  Movie: {.. },
  */
};

Query

query {
   person(id: 42) {
     name
     birthYear
     planet {
       name
     }
     movies {
       title
     }
   }
}
{
  "person": {
  
  }
}
Result
const resolvers = {
  Query: {
    person: (parent, { id }, ctx, infos) => ctx.db.person.findById(id),
    planets: (parent, args, ctx, infos) => ctx.db.planets.findMany(),
    movies: (parent, { size, orderBy}, ctx, infos) => ctx.db.movies.findMany({ size, orderBy }),
  },
  Person: {    
    id: (parent) => parent.id,
    name: (parent) => parent.name,
    birthYear: (parent) => root.birthYear,
    planet: ({ planet_id }, { id }, ctx, infos) => ctx.db.planet.findById(planet_id),
    movies: ({ movie_ids }, { id }, ctx, infos) => ctx.db.movie.findByIds(movie_ids),
  },
  /*
  Planet: { ..},
  Movie: {.. },
  */
};

Query

query {
   person(id: 42) {
     name
     birthYear
     planet {
       name
     }
     movies {
       title
     }
   }
}
{
  "person": {
     "name": "Darth Vader",
     "birthYear": "41.9BBY",
     "planet": { // planet_id = 34
      },
      "movies": [ // movie_ids = [2,4,5,6]
      ]
    }
  }
}
Result
const resolvers = {
  Query: {
    person: (parent, { id }, ctx, infos) => ctx.db.person.findById(id),
    planets: (parent, args, ctx, infos) => ctx.db.planets.findMany(),
    movies: (parent, { size, orderBy}, ctx, infos) => ctx.db.movies.findMany({ size, orderBy }),
  },
  Person: {    
    id: (parent) => parent.id,
    name: (parent) => parent.name,
    birthYear: (parent) => parent.birthYear,
    planet: ({ planet_id }, { id }, ctx, infos) => ctx.db.planet.findById(planet_id),
    movies: ({ movie_ids }, { id }, ctx, infos) => ctx.db.movie.findByIds(movie_ids),
  },
  Planet: {
    name: (root) => root.name
  },
  Movie: {
    title: (root) => root.title
  },
};

Query

query {
   person(id: 42) {
     name
     birthYear
     planet {
       name
     }
     movies {
       title
     }
   }
}
{
  "person": {
     "name": "Darth Vader",
     "birthYear": "41.9BBY",
     "planet": {
        "name": "Tatooine"
      },
      "movies": [
        { "title": "A New Hope" },
        { "title": "The Empire Strikes Back" },
        { "title": "Return of the Jedi" },
        { "title": "Revenge of the Sith" }
      ]
    }
  }
}
Result

Resolvers = Single Source of Truth

3- Server creation

const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

const typeDefs = gql`
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => 'Hello world!',
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = express();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
  console.log('Now browse to http://localhost:4000' + server.graphqlPath)
);

4- Bonus

Directives

directive @deprecated(
  reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUE

type ExampleType {
  firstName: String
  first_name: String @deprecated(reason: "Use `newField`.")
}

Directives

directive @rest(url: String) on FIELD_DEFINITION

type Query {
  people: [Person] @rest(url: "/api/v1/people")
}

Directives

directive @auth(
  requires: Role = ADMIN,
) on OBJECT | FIELD_DEFINITION

enum Role {
  ADMIN
  REVIEWER
  USER
  UNKNOWN
}

type User @auth(requires: USER) {
  name: String
  banned: Boolean @auth(requires: ADMIN)
  canPost: Boolean @auth(requires: REVIEWER)
}

Directives

directive @cache(
  time: Duration,
) on FIELD_DEFINITION

enum Duration {
  TenSeconds
  OneMinute
  OneHour
  OneDay
  Forever
}

type Person {
  name: String!
  planet: String! @cache(duration: Forever)
  movies: [Movie] @cache(duration: OneDay)
}

type Query {
  person(id: Person): Person
  movies: [Movie] @cache(duration: OneDay)
}

Subscriptions

type User {
  person: String
}

type Chat {
  id: ID!
  message: [Message]
}

type Query {
  messages(chatId: ID!): [Message]
}

type Mutation {
  createMessage(chatId: ID!, message: String!): Message
}

type Subscription {
  newMessage(chatId: ID!): Message
}

Dataloader

Han Solo

Chewbaca

Episode 4

Episode 5

Episode 6

Episode 4

Episode 5

Episode 6

Episode 3

Dataloader

Han Solo

Chewbaca

Episode 4

Episode 5

Episode 6

Episode 4

Episode 5

Episode 6

Episode 3

api/falconMilenium
api/person/12
api/person/13
api/movie/4
api/movie/5
api/movie/6
api/movie/3
api/movie/4
api/movie/5
api/movie/6

Dataloader

Han Solo

Chewbaca

Episode 4

Episode 5

Episode 6

Episode 4

Episode 5

Episode 6

Episode 3

api/falconMilenium
api/person/12
api/person/13
api/movie/4
api/movie/5
api/movie/6
api/movie/3
api/movie/4
api/movie/5
api/movie/6

Dataloader

Han Solo

Chewbaca

Episode 4

Episode 5

Episode 6

Episode 4

Episode 5

Episode 6

Episode 3

api/falconMilenium
api/persons?id=12,13
api/movie?3,4,5,6

A lot of possibilities ..

type PersonMutation {
  name(name: String): String
  hairColor(color: Color): Color
  danse(danse: Danse): Danse
  fight(name: String!): Fight
}

type Mutation {
   person(name: String!): PersonMutation
}
mutation {
   person(name: "Anakin Skywalker") {
     name(name: "Darth Vader")
     hairColor(color: Black)
     fight(name: "Obiwan Kenobi") {
       person {
         id
         lifePoint
       }
     }
  }
}

Scalability with Apollo Federation

Typings from server to client

git clone git@github.com:LucM/apollo-wars.git
cd apollo-wars
npm i 
npm run dev

Give it a try!

Thank you!

Introduction to GraphQL

By Luc Momal

Introduction to GraphQL

  • 243