SANS
GERARD
Spoken at 166 events in 37 countries
source: blog
SDL
GQL
Client
GraphQL Server
query
mutation
subscription
Components
Parse
request
Validate
GraphQL Client
Execute
Resolvers
response
// POST '/graphql'
{
  hello
}
// response.data
{
  "hello": "Hello world!"
}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
Analytics
Performance
Logging
Default values
Validation
Caching
Authorisation
Data Formatting
Step 1: Add the directive to the Schema
directive @specifiedBy(
  url: String!                                   
) on SCALAR Identifier
Argument
Location
Use repeatable modifier to apply more than once!
scalar UUIDStep 2: Annotate
scalar UUID @specifiedBy(
  url: "https://bit.ly/rfc412"
)Step 2: Annotate
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 { ... }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 @scalarQuery
GQL
@include
@skip
Client
Query
GQL
@include
@skip
Client
Schema
SDL
@deprecated
@specifiedBy
Server
directive @skip(
  if: Boolean!
) on FIELD | INLINE_FRAGMENT | FRAGMENT_SPREADdirective @skip(
  if: Boolean!
) on FIELD | INLINE_FRAGMENT | FRAGMENT_SPREAD
query ($anonymous: Boolean!) {
  me {
    name @skip(if: $anonymous)
  }
}directive @skip(
  if: Boolean!
) on FIELD | INLINE_FRAGMENT | FRAGMENT_SPREAD
query ($anonymous: Boolean!) {
  me {
    ...on User @skip(if: $anonymous) {
      name 
    }
  }
}directive @skip(
  if: Boolean!
) on FIELD | INLINE_FRAGMENT | FRAGMENT_SPREAD
query ($anonymous: Boolean!) {
  me {
    ...UserDetails @skip(if: $anonymous)
  }
}
fragment UserDetails on User {
  name
}directive @deprecated(
  reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUEdirective @deprecated(
  reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUE
type User {
  uuid: String @deprecated
}Reason is optional and accepts Markdown! Cool!
directive @deprecated(
  reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUE
enum TaskStateEnum {
  IN_PROGRESS @deprecated(reason:"Obsolete")
}Oh lord! This message shows in the Docs
without @defer
request
fast
slow fragment
response
with @defer
request
fast
response
slow fragment
patch
time
Initial load
Update
500ms
700ms
850ms
Initial load
directive @defer(
  if: Boolean = true, label: String                                            
) on FRAGMENT_SPREADAttention! Labels must be unique across queries
type Query {
  getUserById(id: ID!): User                                     
}
type User {
  id: ID!
  name: String!
  performance: EmployeFile 
}
type EmployeFile {
  review: String
}query loadUser($id: ID!) {
  getUserById(id: $id) {
    name
    performance {
      review                                             
    }
  }                                     
}
Geez! This query is really SLOWWW 🤔
query loadUser($id: ID!) {
  getUserById(id: $id) {
    name
    ...EmployeeReview
  }                                     
}
fragment EmployeeReview on User {
  performance {
    review
  }
}Oh! I see…
query loadUser($id: ID!) {
  getUserById(id: $id) {
    name
    ...EmployeeReview @defer(label:"EmployeeReviewDefer")
  }                                     
}
fragment EmployeeReview on User {
  performance {
    review
  }
}Wait what is @defer?
{
  "data": {
    "getUserById": {
      "name": "Elon Musk",                                      
    }
  },
  "hasNext": true
}time
fast
slow fragment
response
patch
request
{
  "label": "EmployeeReviewDefer"
  "path": ["getUserById"]
  "data": {
    "performance": {
      "review": "Exceeds expectations",                                                          
    }
  },
  "hasNext": false
}time
fast
slow fragment
response
patch
request
time
fast
slow fragment
response
patch
request
<Query query={query} errorPolicy="all">{
 ({ loading, error, data, loadingState }) => {
  if (loading) return <Loader />;
  if (error) return <RawResponse error={error} />;
  return (
    <Employee name={data.getUserById} />
    <EmployeeReview performance={data.performance}
      loadingState={loadingState && loadingState.performance}     
    />)
  }
}
</Query>Jeff Bezos
Exceeds expectations
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": [],
}directive @rest(
  url: String!
  property: String!
) on FIELD_DEFINITION
type Query {
  hello: String @rest(
   url: "https://api.chucknorris.io/jokes/random",             
   property: "value"
  )                 
}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;
      }
    }
  });
}type Query {
  hello: String @rest(
   url: "https://api.chucknorris.io/jokes/random",             
   property: "value"
  )                 
}directive @uppercase on FIELD_DEFINITION
type Query {
  hello: String @uppercase                  
}function upperDirectiveTransformer(schema, directiveName) {
  return mapSchema(schema, {
    [MapperKind.OBJECT_FIELD]: fieldConfig => {
      const upperDirective = getDirective(schema, fieldConfig, directiveName)?.[0];      
      if (upperDirective) {
        const { resolve = defaultFieldResolver } = fieldConfig;
        fieldConfig.resolve = async function (source, args, context, info) {
          const result = await resolve(source, args, context, info);
          return result.toUpperCase();
        }
        return fieldConfig;
      }
    }
  });
}type Query {
  hello: String @uppercase                  
}