SANS
GERARD
Developer Evangelist
Developer Evangelist
International Speaker
Spoken at 166 events in 37 countries
Serverless Training
Serverless Training
GraphQL
Dan Schafer
Lee Byron
Nick Schrock
Co-Founders
GraphQL Server
source: blog
Query Lifecycle
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!"
}
GraphQL Query Language (GQL)
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)
GraphQL Community
Why Developers use GraphQL?
- Best tooling for data access
- De-coupled from storage
- Safe: validated and structured
- Collaborative by design
- Supafast for Web and Mobile!
So what is GraphQL secret superpower then?
Tooling?
Introspection?
None of those...
The secret superpower is...
Extensibility
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
Logging
Default values
Validation
Caching
Authorisation
Data Formatting
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
Native Directives
Native GraphQL Directives
Query
GQL
@include
@skip
Client
Native GraphQL Directives
Query
GQL
@include
@skip
Client
Schema
SDL
@deprecated
@specifiedBy
Server
Definition for @skip
directive @skip(
if: Boolean!
) on FIELD | INLINE_FRAGMENT | FRAGMENT_SPREAD
@skip in fields
directive @skip(
if: Boolean!
) on FIELD | INLINE_FRAGMENT | FRAGMENT_SPREAD
query ($anonymous: Boolean!) {
me {
name @skip(if: $anonymous)
}
}
@skip in inline fragments
directive @skip(
if: Boolean!
) on FIELD | INLINE_FRAGMENT | FRAGMENT_SPREAD
query ($anonymous: Boolean!) {
me {
...on User @skip(if: $anonymous) {
name
}
}
}
@skip in fragments
directive @skip(
if: Boolean!
) on FIELD | INLINE_FRAGMENT | FRAGMENT_SPREAD
query ($anonymous: Boolean!) {
me {
...UserDetails @skip(if: $anonymous)
}
}
fragment UserDetails on User {
name
}
Definition for @deprecated
directive @deprecated(
reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUE
@deprecated in fields
directive @deprecated(
reason: String = "No longer supported"
) on FIELD_DEFINITION | ENUM_VALUE
type User {
uuid: String @deprecated
}
Reason is optional and accepts Markdown! Cool!
@deprecated in Enums
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
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 for React
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
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
directive @uppercase on FIELD_DEFINITION
type Query {
hello: String @uppercase
}
Custom Directive: @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;
}
}
});
}
Custom Directive: @uppercase
type Query {
hello: String @uppercase
}
Custom Directive: @uppercase
Unleash
GraphQL secret superpower!
How are you going to use it?
More
ardatan/graphql-tools
contra/graphql-helix
GraphQLs secret superpower
By Gerard Sans
GraphQLs secret superpower
In this talk, we’ll explore a secret API design pattern: GraphQL directives and how to use them without breaking a sweat. Best of all, you will be able to add new features to your schema without having to change your existing code. A fantastic technique to add to your GraphQL toolset!
- 1,855