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 UUID
Step 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 @scalar
Query
GQL
@include
@skip
Client
Query
GQL
@include
@skip
Client
Schema
SDL
@deprecated
@specifiedBy
Server
directive @skip(
if: Boolean!
) on FIELD | INLINE_FRAGMENT | FRAGMENT_SPREAD
directive @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_VALUE
directive @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_SPREAD
Attention! 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
}