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
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
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
- Apollo link for Websockets
- WebSocket subscriptions
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