SANS

GERARD

Developer Evangelist

Developer Evangelist

International Speaker

Spoken at 180 events in 39 countries

Serverless Training

Serverless Training

GraphQL

Dan Schafer

Lee Byron

Nick Schrock

Co-Founders

GraphQL Server

source: blog


// POST '/graphql'
{
  hello
}

// response.data
{
  "hello": "Hello world!"
}

GraphQL Query Language (GQL)

GraphQL Timeline

2012

GraphQL created at Facebook

2015

GraphQL is open sourced

Relay Classic is open sourced

2016

New GraphQL website graphql.org

First GraphQL Summit

GitHub announces GraphQL API

2017

Relay Modern 1.0

Apollo Client 2.0

Apollo Client 1.0

2019

Apollo Client 2.5

Apollo Federation

2020

Apollo Client 3.0

Apollo Explorer

2022 

Apollo Federation 2

graphql.org

graphql.com

Implementations

Why use GraphQL?

Superfast

Safe

Best

Tooling

Why use GraphQL?

De-coupled Storage

Collaborative

GraphQL Schema

Schema Overview

  • Entry points
    • Query
    • Mutation
    • Subscription
  • Features
    • Validation, Documentation and Introspection

Type System: Scalars

  • Scalar Types: Int, Float, String, Boolean, ID
  • Custom scalars
    • Create your own
    • Use a ​Library (GraphQL Scalars: Date, DateTime, URL, UUID and more)

Type System: Object Types

  • Native: Query, Mutation, Subscription
  • Custom: Post
  • Each Type has fields
    • id: ID
    • name: String

Special Syntax

  • Mandatory: String!, Post!
  • Optional: String, Post
  • Lists: [String], [Post]
schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

type Query {
  allTodos(first: Int): [Todo]
}

type Todo {  
  id: ID!
  text: String!
  complete: Boolean!
}

Types

Field 

Field 

Field 

Arguments

Mandatory

List

Types

Types

This is optional

Schema Definition Language (SDL)

Introspection

apis-guru/graphql-voyager

ardatan/graphql-tools

Query Syntax

Anonymous Query

Named Query

QUERY VARIABLES

Selection Sets

Fragments

Using Fragments

Exercises. Oh no!

Apollo Studio

n9.cl/confoo-01a

GraphQL Server

Query Lifecycle

SDL

GQL

Client

GraphQL Server

query

mutation

subscription

Components

Parse

request

Validate

GraphQL Client

Execute

Resolvers

response

Schema: Type Definitions

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

Resolvers

const resolvers = {
  Query: {
    hello: async (parent, args, context, info) {
      return "Hello world!";
    },
  },
};

ApolloServer

const { ApolloServer } = require("apollo-server");

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

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

Resolvers

  • Each field in a GraphQL schema is backed by a resolver
  • Resolver parameters
    • root/parent, result previous call
    • args, query parameters
    • context, share information
    • info, query AST

 

Resolvers Example

const resolvers = {
  Query: {
    feed(parent, args, ctx, info) {
      return ctx.db.query.posts({ where: { isPublished: true } }, info)
    },
    drafts(parent, args, ctx, info) {
      return ctx.db.query.posts({ where: { isPublished: false } }, info)
    },
    post(parent, { id }, ctx, info) {
      return ctx.db.query.post({ where: { id } }, info)
    },
  },
}

Query Execution

contra/graphql-helix

Codesandbox

n9.cl/confoo-02a

GraphQL Directives

GraphQL Directives

  • Annotations to extend your GraphQL API by adding meta-data or custom logic
  • Available in the client via Queries (GQL) or the server via the Schema (SDL)

What can you do with Directives?

Analytics

Performance

background Layer 1

Logging

background Layer 1

Default values

background Layer 1

Validation

Caching

Authorisation

Data Formatting

background Layer 1

Step 1: Add the directive to the Schema

Creating a Directive

directive @specifiedBy(
  url: String!                                   
) on SCALAR 

Identifier

Argument

Location

Use repeatable modifier to apply more than once!

scalar UUID

Creating a Directive

Step 2: Annotate

scalar UUID @specifiedBy(
  url: "https://bit.ly/rfc412"
)

Creating a Directive

Step 2: Annotate

Client: 8 extension locations

query getUser($var: ID @variable) @query {
  me {
    id @field
    ... UserDetails @fragment_spread
    ... on User @inline_fragment { ... }
  }
}
fragment UserDetails on User @fragment_definition { ... }

mutation AddVote @mutation { ... }
subscription VotesSubscription @subscription { ... }

Server: 11 extension locations

schema @schema { ... }
type Query @object {
  getUser(id: ID! @argument):User @field_definition 
}
input BlogPostContent @input_object {
  title: String @input_field_definition 
}
enum MediaFormat @enum {
  VIDEO @enum_value
}
union SearchResult @union = Book | Movie
interface Book @interface { ... }
scalar DateTime @scalar

Better UX with @defer

Faster with @defer

without @defer

request

fast

slow fragment

response

with @defer

request

fast

response

slow fragment

patch

time

Initial load

Update

500ms

700ms

850ms

Initial load

@defer (experimental) 

  • Improve User Experience
  • Postpone non-essential data
  • Automatically split results for faster response times via HTTP-Multipart patches
  • Minimise impact of slow or large fragments
  • Fragments can be of one or more fields

Definition for @defer

directive @defer(
  if: Boolean = true, label: String                                            
) on FRAGMENT_SPREAD

Attention! Labels must be unique across queries

Fixing slow queries

type Query {
  getUserById(id: ID!): User                                     
}
type User {
  id: ID!
  name: String!
  performance: EmployeFile 
}
type EmployeFile {
  review: String
}

Identify the root cause

query loadUser($id: ID!) {
  getUserById(id: $id) {
    name
    performance {
      review                                             
    }
  }                                     
}

Geez! This query is really SLOWWW 🤔 

Improving slow fields

query loadUser($id: ID!) {
  getUserById(id: $id) {
    name
    ...EmployeeReview
  }                                     
}
fragment EmployeeReview on User {
  performance {
    review
  }
}

Oh! I see…

Example Request

query loadUser($id: ID!) {
  getUserById(id: $id) {
    name
    ...EmployeeReview @defer(label:"EmployeeReviewDefer")
  }                                     
}
fragment EmployeeReview on User {
  performance {
    review
  }
}

Wait what is @defer?

Initial Response

{
  "data": {
    "getUserById": {
      "name": "Elon Musk",                                      
    }
  },
  "hasNext": true
}

time

fast

slow fragment

response

patch

request

Patch Response

{
  "label": "EmployeeReviewDefer"
  "path": ["getUserById"]
  "data": {
    "performance": {
      "review": "Exceeds expectations",                                                          
    }
  },
  "hasNext": false
}

time

fast

slow fragment

response

patch

request

Client Code

time

fast

slow fragment

response

patch

request

Jeff Bezos

Exceeds expectations

<app-employee [loading] [data.getUserById]>
  <app-review [loadingState] [data.performance]>
  </app-review>
</app-employee>

Custom Directives

api.chucknorris.io

{
  "id": "dnKbjh3RSfGXfcp5Rwiy9Q",
  "value": "The Grand Canyon was caused by Chuck Norris pissing there--Once.",
  "created_at": "2020-01-05 13:42:19.897976",
  "updated_at": "2020-01-05 13:42:19.897976",
  "url": "https://.../dnKbjh3RSfGXfcp5Rwiy9Q",
  "icon_url": "https://.../chuck-norris.png",
  "categories": [],
}

api.chucknorris.io JSON response 

directive @rest(
  url: String!
  property: String!
) on FIELD_DEFINITION

type Query {
  hello: String @rest(
   url: "https://api.chucknorris.io/jokes/random",             
   property: "value"
  )                 
}

Custom Directive: @rest

function restDirectiveTransformer(schema) {
  return mapSchema(schema, {
    [MapperKind.OBJECT_FIELD]: fieldConfig => {
      const restDirective = getDirective(schema, fieldConfig, "rest")?.[0];
      if (restDirective) {
        const { url, property } = restDirective;
        fieldConfig.resolve = async () => {
          const response = await (await fetch(url)).json();
          return response[property];
        };
        return fieldConfig;
      }
    }
  });
}

Custom Directive: @rest

type Query {
  hello: String @rest(
   url: "https://api.chucknorris.io/jokes/random",             
   property: "value"
  )                 
}

Custom Directive: @rest

Apollo Client

Apollo

Cache

Apollo Client

Apollo

Link

Apollo

Server

My first Apollo App

n9.cl/confoo-04

Apollo Cache

Packages

  • apollo-cache-inmemory
  • apollo-cache-hermes
  • apollo-cache-persist

InMemoryCache

  • Normalised and flattened
  • readQuery, writeQuery
  • readFragment, writeFragment

Apollo Cache Example

query todos {
  allTodoes { 
    id 
    text 
    complete 
    __typename
  }
}
{
 "data": {
   "allTodoes": [
    {
     "id": "1",
     "text": "Learn GraphQL 📚",
     "complete": false,
     "__typename": 'Todo',
   }, 
   {
     "id": "2",
     "text": "Learn Apollo Client ✨👨‍🚀",
     "complete": true,
     "__typename": 'Todo',
   }]
}

Query

Result

Normalised and flattened

{
 ROOT_QUERY: {
  allTodoes: ['Todo:1', 'Todo:2'],
 },
 'Todo:1': {
   id: '1', 
   text: 'Learn GraphQL 📚', 
   completed: false
 },
 'Todo:2': {
   id: '2', 
   text: 'Learn Apollo Client ✨👨‍🚀', 
   completed: false
 }
}

read/writeQuery

read/writeFragment

__typename:id

const query = gql`query AllTodos {
  allTodoes { id text complete }
}`;
const data = store.readQuery({ query });
const newTodo = {
  id: createTodo.id,
  text: input.value,
  complete: false,
  __typename: "Todo"
};
store.writeQuery({
  query,
  data: {
    allTodoes: [...data.allTodoes, newTodo],
  },
});
App.js

Retrieveing todos from cache

Apollo Link

Packages

  • apollo-link
  • apollo-link-http
  • apollo-link-ws
  • apollo-link-error
  • apollo-link-batch
  • apollo-link-polling
  • apollo-link-retry

Packages (more)

  • apollo-link-state
  • apollo-link-rest
  • apollo-link-offline

Apollo Link

  • concat(link1, link2)
  • split(expr, link1, link2)
  • from([link1, link2])
import ApolloClient from 'apollo-boost';

const client = new ApolloClient({
  uri: 'https://your-server/graphql',
  request: operation => {
    operation.setContext(context => ({
      headers: {
        ...context.headers,
        authorization: localStorage.getItem('authtoken')
      }
    }))
  }
});

Set security headers

Using  GraphQL

GraphQL Server

source: blog

Adding Realtime

GraphQL Server

source: blog

Dependencies

import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';

export const client = new ApolloClient({
  link: setupLink(),
  cache: new InMemoryCache()
});

Subscriptions Setup

function setupLink() {
  const httpLink = new HttpLink({ uri: URI });
  const wsLink = new WebSocketLink({
    uri: URI_WS,
    options: { reconnect: true }
  });
  const isSubscription = ({ query }) => {
    const { kind, operation } = getMainDefinition(query);
    return kind==='OperationDefinition' && operation==='subscription';
  }
  const link = split(
    isSubscription,
    /* if true use  */ wsLink,
    /* otherwise */ httpLink,
  );
  return link;
}

Subscriptions Setup 2

schema { 
  subscription: Subscription 
}

type Mutation {
  createTodo(text: String!, complete: Boolean!): Todo
  deleteTodo(id: ID!): Todo
}

type Subscription {
  Todo(filter: [CREATED, UPDATED, DELETED]): {
    mutation: [CREATED, UPDATED, DELETED]
    node: Todo
    updatedFields: [String!]
    previousValues: Todo
  }
}

Todos Schema

project.graphcool
query.subscribeToMore({
  document: gql`
    subscription {
      Todo(filter: { mutation_in: [CREATED] }) {
        node { id complete text }
      }
    }
  `,
  updateQuery: (state, { subscriptionData }) => {
    ...
  }

subscribeToMore

updateQuery: (state, { subscriptionData }) => {
  const {node} = subscriptionData.data.Todo;

  if (!state.allTodoes.find(todo => todo.id === node.id)) {
    state.allTodoes.push(node);                
  }
  return {
    allTodoes: todos
  }
}

updateQuery

Community

GraphQL Community

Influencers

Johannes Schickling

Peggy Rayzis

Sashko Stubailo

Nikolas Burk

More

Fullstack GraphQL

By Gerard Sans

Fullstack GraphQL

In this workshop we will create a Fullstack GraphQL app starting from the Server and building our way up to the client! We will cover everything you need to successfully adopt GraphQL across your stack from client to backend including tooling and best practices. You will learn how to build and design a GraphQL Server starting by defining the GraphQL Schema using types and relations. Moving to the client side, we will create a simple client to demonstrate common usage. As we implement the different features we will introduce GraphQL query syntax including queries, mutations, alias, fragments and directives. At this point we will review how client and server communicate, what tooling is available to track usage and improve performance and how to add authorisation and authentication. Finally we will focus on designing real-time features and sharing best practices to improve performance and leverage scalability.

  • 131