1 of 58

GraphQL

Towards a Universal Query Language

2 of 58

GraphQL

  • It’s great for performance�
  • It’s great for backward compatibility�
  • It’s great for the developer experience�
  • It’s even great for <insert something grandiose here>

3 of 58

4 of 58

The plan

  • Schema Definition Language (SDL)�
  • Resolvers�
  • How a query is executed�
  • Graph “all the things”�
  • “don’t try this at home”

5 of 58

G’day, how’s it goin’?

6 of 58

👋

  • I’m Michael Mifsud�
  • Senior engineer on the Platforms team at Redbubble�
  • Karaoke enthusiast 🎤
  • Maintainer for LibSass and Node Sass

7 of 58

Schema Definition Language

8 of 58

type Conference {

id: Int!

name: String!

speakers: [Speaker!]

}

type Speaker {

id: Int!

name: String!

emoji: String!

conference: [Conference!]}

9 of 58

type Query {

conferences(): [Conference!]

speakers(emoji: String!): [Speaker!]

}

type Mutation {

speak(speakerID: Int, conferenceID: Int): [Conference!]�}

10 of 58

Resolvers

11 of 58

function resolver(parent, args, context) {

};

12 of 58

const resolvers = {

Query: {

speakers(parent, args, context) {

return [

{ id: 15, name: "Una Kravets", emoji: "🦄" },

{ id: 37, name: "Michael Mifsud", emoji: "🍕" }

]

.filter(speaker => speaker.emoji === args.emoji)

}

}};

13 of 58

const resolvers = {

Query: { ... },

Mutation: {

speak(parent, args, context) {

const conferences = context.conferences;

return conferences.loadById(args.conferenceID)

.then(c => c.addSpeaker(speakerID))

.then(c => s.save())

.then(c => conferences.loadBySpeaker(args.speakerID))

}

}

};

14 of 58

Resolvers for profit

15 of 58

const resolvers = {

Query: {

speakers() {

return [

{ id: 1, name: "Una Kravets", emoji: "🦄" },

{ id: 2, name: "Michael Mifsud", emoji: "🍕" }

]

}

}

};

16 of 58

The default resolver

17 of 58

const resolver = (parent, args, context) => {

return typeof parent[field] === "function"

? parent[field](args)

: parent[field];};

18 of 58

const resolvers = {

Query: { ... },

Speaker: {

id(parent) => parent.id,

name(parent) => parent.name,

emoji(parent) => parent.emoji,

conference(parent) => parent.conferences,

}

};

19 of 58

APIs

20 of 58

const resolvers = {

Query: {

speakers(parent, args, context) {

return jQuery.ajax({

url: `https://speakers.io/${args.emoji}`,

headers: {

'Content-Type': 'application/json',

'X-API-KEY': context.secrets.apiKey,

}

});

}

}

};

21 of 58

const resolvers = {

Query: { ... },

Speaker: {

conferences(parent, args, context) {

return fetch(`https://conferences.io/${parent.id}`, {

headers: {

'Content-Type': 'application/json',

'X-API-KEY': context.secrets.apiKey

}

})

.then((resp) => resp.json())

}

}};

22 of 58

{

speakers(emoji: "🍕") {

name

conferences {

name

}

}}

{

"speakers": [{

"name": "Michael Mifsud",

"conferences": [{

"name": "JSConf EU"

}]

}]}

23 of 58

Databases

24 of 58

const resolvers = {

Query: {

speakers(parent, args, context) {

return context.db.loadSpeakerByEmoji(args.emoji);

}

}};

25 of 58

Datastores

26 of 58

const resolvers = {

Query: {

speakers(parent, args, context) {

return context.db.loadSpeakerByEmoji(args.emoji);

}

},

Speaker: {

conferences(parent, args, context) {

return context.elasticSearch.execute(...);

},

emoji(parent, args, context) {

return context.redis.get(...);

}

}

};

27 of 58

The file system

28 of 58

Spreadsheets!

29 of 58

const resolvers = {

Query: {

speakers(parent, args, context) {

return fs.promises.readFile('speakers.csv')

.then(context.csv.parse)

.then(data => data.filter(({ emoji }) => {

return emoji === args.emoji;

}));

}

}};

30 of 58

Javascript APIs

31 of 58

const resolvers = {

Query: {

configuration(parent, args, context) {

return fs.promises.readFile(`config/${args.type}.json`)

}

}};

32 of 58

{

configuration(type: "keys") {

speakerAPIKey

conferenceAPIKey

databaseURN

}}

33 of 58

GraphQL

34 of 58

server.on('request', (req) => {

const client = new GraphQLServer({

schema,

resolvers,

context: { configuration: getConfiguration() },

});

client.handle(req);

});

35 of 58

✋🎤

36 of 58

Everything is a GraphQL

  • What if the Node.js APIs were GraphQL?�
  • What if our Javascript APIs were GraphQL?�
  • What if the browser APIs were GraphQL?�
  • What if we GraphQL all the things????

37 of 58

Resolvers for fun

38 of 58

Don’t try this at home

39 of 58

GraphQL in the browser

40 of 58

But why?

  • Browser feature fragmentation�
  • Browser API fragmentation�
  • Progressive enhancement�
  • Fun

41 of 58

Datastore

  • Web Storage�
  • Web SQL Database�
  • IndexedDB�
  • IndexedDB 2.0�
  • Cookies�
  • URLs

42 of 58

const store = require('store');

const resolvers = {

Query: {

speakers(parent, args, context) {

return store.get('speakers')

.then(data => data.filter(({ emoji }) => {

return emoji === args.emoji;

}));

}

}};

43 of 58

APIs

44 of 58

const resolvers = {

Query: {

speakers(parent, args, context) {

return fetch ? fetch(...) : new XMLHttpRequest(...)

}

}};

45 of 58

GraphQL in the browser

46 of 58

The DOM

47 of 58

type Query {

document: Document

}

type Document {

querySelector: Element

querySelectorAll: NodeList

}

type Element {

tagName: String!

}��type NodeList { ... }

48 of 58

const resolvers = {

Query: {

document() { return window.document }

},

Document: {

querySelector(parent, args, context) {

return parent.querySelector(args.selectors);

}

},

Element: {

tagName(parent, args, context) {

return parent.tagName;

}

}

};

49 of 58

const speakers = client.query(`{

window {

fetch(url: "https://speakers.io/🇩🇪") {

json

}

}

}`);

50 of 58

const tagName = client.query(`{

document {

querySelectorAll(selectors: "body") {

tagName

}

}

}`);

51 of 58

const conference = client.query(`{

fs {

readFile(path: "conferences.csv") {

csv

}

}

}`);

52 of 58

type Query {

document: Document

}

type Document {

querySelector: Element

querySelectorAll: NodeList

}

type Element {

tagName: String!

}��type NodeList { ... }

53 of 58

Mo schema, mo problems

54 of 58

const resolver = (parent, args, context) => {

return typeof parent[field] === "function"

? parent[field](args)

: parent[field];};

55 of 58

const resolvers = {

Query: {

document() { return window.document }

},

Document: {

querySelector(parent, args, context) {

return parent.querySelector(args.selectors);

}

},

Element: {

tagName(parent, args, context) {

return parent.tagName;

}

}

};

56 of 58

57 of 58

In closing

58 of 58

Thank you