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