What/how to do with GraphQL?
Valentyn Ostakh
#27
query {
me {
firstName
lastName
company
position
socialLinks {
name
url
}
}
}
{
"data": {
"me": {
"firstName": "Valentyn",
"lastName": "Ostakh",
"company": "RubyGarage",
"position": "Ruby/JS developer",
"socialLinks": [
{
"name": "facebook",
"url": "https://facebook.com/valikos"
},
{
"name": "twitter",
"url": "https://twitter.com/valikos_ost"
},
{
"name": "github",
"url": "https://github.com/valikos"
}
]
}
}
}
Agenda
• Introduction into GraphQL

• GraphQL SDL(Schema Definition Language)

• GraphQL Execution

• Pitfalls
What is
GraphQL?
🤔
What is GraphQL?
• A query language for API

• A type system of defined data

• A platform of both backend + frontend applications
More About GraphQL
• GraphQL was developed internally by Facebook in 2012 before being
publicly released in 2015

• The idea of GraphQL became from the frontend team

• GraphQL services can be written in any language

• GraphQL is transport-agnostic

• GraphQL represents data in graphs
GraphQL SDL
What to do with GraphQL?
🗺
GraphQL SDL
GraphQL schema is at the center of any GraphQL server
implementation.

GraphQL schema describes the functionality available to the
clients who connect to it
GraphQL SDL
• Schema

• Object Types

• Fields

• Scalars

• Enums

• Lists

• Non-Null
• Directives

• Aliases

• Interfaces

• Unions

• Fragments

• Input Objects
Object Type
GraphQL Object Type
{
"data": {
"character": {
"firstName": "Peter",
"lastName": "Griffin",
"friends": [
{
"firstName": "Brian",
"lastName": null
},
{
"firstName": "Homer",
"lastName": "Simpson"
}
]
}
}
}
query {
character {
firstName
lastName
friends {
firstName
lastName
}
}
}
GraphQL Object Type
{
"data": {
"character": {
"firstName": "Peter",
"lastName": "Griffin",
"friends": [
{
"firstName": "Brian",
"lastName": null
},
{
"firstName": "Homer",
"lastName": "Simpson"
}
]
}
}
}
query {
character {
firstName
lastName
friends {
firstName
lastName
}
}
}
GraphQL Object Type
type Character {
firstName: String!
lastName: String
friends: [Character!]
}
module Types
class CharacterType < BaseObject
field :first_name, String, null: false
field :last_name, String, null: true
field :friends, [Types::CharacterType], null: true
end
end
Fields
GraphQL Fields
type Character {
firstName: String!
lastName: String
friends: [Character!]
}
module Types
class CharacterType < BaseObject
field :first_name, String, null: false
field :last_name, String, null: true
field :friends, [Types::CharacterType], null: true
end
end
GraphQL Fields
{
"data": {
"character": {
"firstName": "Peter",
"lastName": "Griffin",
"friends": [
{
"firstName": "Brian",
"lastName": null
},
{
"firstName": "Homer",
"lastName": "Simpson"
}
]
}
}
}
query {
character {
firstName
lastName
friends {
firstName
lastName
}
}
}
GraphQL Fields
{
"data": {
"character": {
"firstName": "Peter",
"lastName": "Griffin",
"friends": [
{
"firstName": "Brian",
"lastName": null
},
{
"firstName": "Homer",
"lastName": "Simpson"
}
]
}
}
}
query {
character {
firstName
lastName
friends {
firstName
lastName
}
}
}
GraphQL Fields
• Arguments

• Resolvers
GraphQL Fields: Arguments
{
"data": {
"character": {
"firstName": "Jon",
"lastName": "wonS",
"friends": [
{
"firstName": "Samwell",
"lastName": "Tarly"
}
]
}
}
}
query {
character {
firstName
lastName(reverse: true)
friends(last: 1) {
firstName
lastName
}
}
}
GraphQL Fields: Arguments
{
"data": {
"character": {
"firstName": "Jon",
"lastName": "wonS",
"friends": [
{
"firstName": "Samwell",
"lastName": "Tarly"
}
]
}
}
}
query {
character {
firstName
lastName(reverse: true)
friends(last: 1) {
firstName
lastName
}
}
}
GraphQL Fields: Arguments
type Character {
firstName: String!
lastName(reverse: Boolean): String
friends(last: Int): [Character!]
}
module Types
class CharacterType < BaseObject
field :first_name, String, null: false
field :last_name, String, null: true do
argument :reverse, Boolean, required: false
end
field :friends, [Types::CharacterType], null: true do
argument :last, Integer, required: false
end
end
end
GraphQL Fields: Arguments
type Character {
firstName: String!
lastName(reverse: Boolean): String
friends(last: Int): [Character!]
}
module Types
class CharacterType < BaseObject
field :first_name, String, null: false
field :last_name, String, null: true do
argument :reverse, Boolean, required: false
end
field :friends, [Types::CharacterType], null: true do
argument :last, Integer, required: false
end
end
end
GraphQL Fields: Resolvers
module Types
class CharacterType < BaseObject
field :first_name, String, null: false
field :last_name, String, null: true
field :full_name, String, null: false
end
end
GraphQL Fields: Resolvers
module Types
class CharacterType < BaseObject
field :first_name, String, null: false
field :last_name, String, null: true
field :full_name, String, null: false
def full_name
[object.full_name, object.last_name].join(' ')
end
end
end
GraphQL Fields: Resolvers
module Types
class CharacterType < BaseObject
field :first_name, String, null: false
field :last_name, String, null: true
field :full_name, String, null: false, resolver: Resolvers::FullName
end
end
GraphQL Fields: Resolvers
module Resolvers
class Character < Resolvers::Base
type Character, null: false
argument :id, ID, required: true
def resolve(id:)
Character.find(id)
end
end
end
Scalars
GraphQL Scalars
A GraphQL object type has a name and fields
but at some point those fields have to
become some concrete data. 

That's where the scalar types come in: they
represent the leaves of the query.
GraphQL Scalars: Built-in Types
• Int: A signed 32‐bit integer

• Float: A signed double-precision floating-point value

• String: A UTF‐8 character sequence

• Boolean: true or false

• ID: The ID scalar type represents a unique identifier, often used to
refetch an object or as the key for a cache. The ID type is
serialized in the same way as a String; however, defining it as
an ID signifies that it is not intended to be human‐readable
GraphQL Scalars
In most GraphQL service implementations,
there is also a way to specify custom scalar
types.
GraphQL Scalars: GraphQL-Ruby Scalar Types
• Int: like a JSON or Ruby integer

• Float: like a JSON or Ruby floating point decimal

• String: like a JSON or Ruby string

• Boolean: like a JSON or Ruby boolean (true or false)

• ID: which a specialized String for representing unique object
identifiers

• ISO8601DateTime: an ISO 8601-encoded datetime
GraphQL Scalars: Custom Scalar
class Types::Url < Types::BaseScalar
description "A valid URL, transported as a string"
def self.coerce_input(input_value, context)
url = URI.parse(input_value)
if url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS)
url
else
raise GraphQL::CoercionError, "#{input_value.inspect} is not a valid URL"
end
end
def self.coerce_result(ruby_value, context)
ruby_value.to_s
end
end
GraphQL Scalars: Custom Scalar
class Types::Url < Types::BaseScalar
description "A valid URL, transported as a string"
def self.coerce_input(input_value, context)
url = URI.parse(input_value)
if url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS)
url
else
raise GraphQL::CoercionError, "#{input_value.inspect} is not a valid URL"
end
end
def self.coerce_result(ruby_value, context)
ruby_value.to_s
end
end
Enums
GraphQL Enums
Enumerations are similar to custom scalars with the limitation that their
values can only be one of a pre-defined list of strings.
GraphQL Enums
{
"data": {
"character": {
"firstName": "Peter",
"role": "FATHER",
"friends": [
{
"firstName": "Brian",
"role": "PET"
},
{
"firstName": "Stewie",
"role": "SON"
}
]
}
}
}
query {
character {
firstName
role
friends {
firstName
role
}
}
}
GraphQL Enums
enum RoleEnum {
FATHER
MOTHER
SON
DAUGHTER
DOG
}
class Types::RoleEnum < Types::BaseEnum
value ‘FATHER'
value ‘MOTHER'
value ‘SON'
value ‘DAUGHTER'
value ‘PET'
end
GraphQL Enums
enum RoleEnum {
FATHER
MOTHER
SON
DAUGHTER
DOG
}
class Types::RoleEnum < Types::BaseEnum
value 'FATHER', value: 1
value 'MOTHER', value: 2
value 'SON', value: 3
value 'DAUGHTER', value: 4
value 'PET', value: 5
end
GraphQL Enums
enum RoleEnum {
FATHER
MOTHER
SON
DAUGHTER
DOG
}
class Types::RoleEnum < Types::BaseEnum
value 'FATHER', value: :father
value 'MOTHER', value: :mom
value 'SON', value: :son
value 'DAUGHTER', value: :daughter
value 'PET', value: :animal
end
Lists
GraphQL Lists
{
"data": {
"character": {
"firstName": "Peter",
"lastName": "Griffin",
"friends": [
{
"firstName": "Brian",
"lastName": null
},
{
"firstName": "Homer",
"lastName": "Simpson"
}
]
}
}
}
query {
character {
firstName
lastName
friends {
firstName
lastName
}
}
}
GraphQL Lists
type Character {
firstName: String!
lastName: String
friends: [Character!]
}
module Types
class CharacterType < BaseObject
field :first_name, String, null: false
field :last_name, String, null: true
field :friends, [Types::CharacterType], null: true
end
end
Non-Null
GraphQL Non-Null
In the GraphQL type system all types are nullable by default. 

This means that a type like Int can take any integer (1, 2, etc.)
or null which represents the absence of any value. 

However, the GraphQL type system allows you to make any type non-
null which means that the type will never produce a null value. 

When using a non-null type there will always be a value.
GraphQL Non-Null
type Character {
firstName: String!
lastName: String
friends: [Character!]!
}
type Character {
firstName: String!
lastName: String
friends: [Character]!
}
type Character {
firstName: String!
lastName: String
friends: [Character!]
}
type Character {
firstName: String!
lastName: String
friends: [Character]
}
GraphQL Non-Null
module Types
class CharacterType < BaseObject
field :first_name, String, null: false
field :last_name, String, null: true
field :friends, [Types::CharacterType, null: true], null: true
field :friends, [Types::CharacterType, null: true], null: false
field :friends, [Types::CharacterType, null: false], null: true
field :friends, [Types::CharacterType, null: false], null: false
end
end
Unions
GraphQL Unions
{
search(in: "Adventure Time") {
__typename
... on Character {
firstName
}
... on land {
name
}
... on Building {
type
}
}
}
{
"data": {
"search": [
{
"__typename": "Character",
"firstName": "Finn"
},
{
"__typename": "Land",
"name": "Land of Ooo"
},
{
"__typename": "Building",
"type": "Fort"
}
]
}
}
GraphQL Unions
union Result = Character | Land | Building
type Character {
firstName: String!
}
type Building {
type: String!
}
type Land {
name: String!
}
type Query {
search: [Result]
}
GraphQL Unions
union Result = Character | Land | Building
type Character {
firstName: String!
}
type Building {
type: String!
}
type Land {
name: String!
}
type Query {
search: [Result]
}
GraphQL Unions
class Types::ResultType < Types::BaseUnion
possible_types Types::CharacterType, Types::LandType, Types::BuildingType
# Optional: if this method is defined, it will override `Schema.resolve_type`
def self.resolve_type(object, context)
if object.is_a?(Character)
Types::CharacterType
elsif object.is_a?(Land)
Types::LandType
else
Types::BuildingType
end
end
end
Interfaces
GraphQL Interfaces
interface Node {
id: ID!
}
interface Person {
firstName: String!
lastName: String
}
type Land implements Node {
id: ID!
name: String!
}
type Character implements Node & Person {
id: ID!
firstName: String!
lastName: String
}
GraphQL Interfaces
module Types::Interfaces::Node
include Types::BaseInterface
field :id, ID, null: false
end
module Types::Interfaces::Person
include Types::BaseInterface
field :first_name, String, null: false
field :last_name, String, null: true
end
module Types
class CharacterType < BaseObject
implements Types::Interfaces::Node
implements Types::Interfaces::Person
end
end
Fragments
GraphQL Fragments
query {
character {
firstName
lastName
friends {
firstName
lastName
}
}
}
GraphQL Fragments
fragment CharacterFragment on Character {
firstName
lastName
}
query {
character {
...CharacterFragment
friends {
...CharacterFragment
}
}
}
Aliases
GraphQL Aliases
query {
hero: character(hero: true) {
firstName
}
antihero: character(hero: false) {
firstName
}
}
{
"data": {
"hero": {
"firstName": "Harrison",
},
"antihero": {
"firstName": "Sebastian",
}
}
}
Directives
GraphQL Directives
• @deprecated(reason: String) - marks fields as deprecated with messages

• @skip(if: Boolean!) - GraphQL execution skips the field if true by not calling the resolver

• @include(id: Boolean!) - Calls resolver for annotated field if true
GraphQL provides several built-in directives:
GraphQL Directives
query {
character {
firstName
lastName
friends @skip(if: $isAlone){
firstName
lastName
}
}
}
type Character {
firstName: String!
lastName: String
surname: String @deprecated(reason: "Use lastName. Will be removed...")
friends: [Character!]
}
GraphQL Directives: Custom Directive
class Directives::Rest < GraphQL::Schema::Directive
description "..."
locations(
GraphQL::Schema::Directive::FIELD,
GraphQL::Schema::Directive::FRAGMENT_SPREAD,
GraphQL::Schema::Directive::INLINE_FRAGMENT
)
argument :url, String, required: true, description: "..."
def self.include?(obj, args, ctx)
# implementation
end
def self.resolve(obj, args, ctx)
# implementation
end
end
query {
character @rest(url: "/path") {
firstName
lastName
friends {
firstName
lastName
}
}
}
GraphQL Directives: Custom Directive
Input Objects
GraphQL Input Objects
mutation CreateCharacter(
$firstName: String!
$lastName: String
$role: RoleEnum!
) {
createCharacter(
firstName: $firstName
lastName: $lastName
role: $role
) {
firstName
lastName
role
}
}
mutation CreateCharacter(
$input: CharacterAttributes!
) {
createCharacter(input: $input) {
firstName
lastName
role
}
}
GraphQL Input Objects
input CharacterAttributes {
firstName: String!
lastName: String
role: RoleEnum!
}
class Types::CharacterAttributes < Types::BaseInputObject
argument :first_name, String, required: true
argument :last_name, String, required: false
argument :role, Types::RoleEnum, required: false
end
Schema
🍒
GraphQL Schema
schema {
query: Query
}
GraphQL Schema
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
class MySchema < GraphQL::Schema
# Required:
query Types::QueryType
# Optional:
mutation Types::MutationType
subscription Types::SubscriptionType
end
GraphQL Schema
class Types::QueryType < GraphQL::Schema::Object
field :character, Types::CharacterType, resolver: Resolvers::Character
end
# Similarly:
class Types::MutationType < GraphQL::Schema::Object
# ...
end
# and
class Types::SubscriptionType < GraphQL::Schema::Object
# ...
end
type Query {
character(id: ID!): CharacterType
}
What GraphQL SDL gives us?
What GraphQL SDL gives us?
Documentation
What GraphQL SDL gives us?
Documentation is the first class citizen in GraphQL
GraphQL Execution
How to do with GraphQL?
🚀
When executing a GraphQL query one of the very first things the server needs to do
is transform the query (currently a string) into something it understands.
Transport
Transport
• HTTP POST

• one endpoint (e.g. /graphql)

• response code 200 OK
Transport
GraphQL-Ruby Phases of Execution
• Tokenize: splits the string into a stream of tokens

• Parse: builds an abstract syntax tree (AST) out of the stream of tokens

• Validate: validates the incoming AST as a valid query for the schema

• Rewrite: builds a tree of nodes which express the query in a simpler way than the
AST

• Analyze: if there are any query analyzers, they are run

• Execute: the query is traversed, resolve functions are called and the response is built

• Respond: the response is returned as a Hash
Tokenize & Parsing
GraphQL has its own grammar.

We need this rules to split up the query.

 GraphQL Ruby uses a tool called Ragel to generate its lexer.
How to understand that we have valid data?
How to verify incoming data?
• Breaks up a stream of characters (in our case a GraphQL query) into
tokens

• Turns sequences of tokens into a more abstract representation of the
language
Lexer
query {
character {
firstName
lastName
friends {
firstName
lastName
}
}
}
GraphQL.scan(query) =>
[
(QUERY "query" [2:1]),
(LCURLY "{" [2:7]),
(IDENTIFIER "character" [3:3]),
(LCURLY "{" [3:13]),
(IDENTIFIER "firstName" [4:5]),
(IDENTIFIER "lastName" [5:5]),
(IDENTIFIER "friends" [6:5]),
(LCURLY "{" [6:13]),
(IDENTIFIER "firstName" [7:7]),
(IDENTIFIER "lastName" [8:7]),
(RCURLY "}" [9:5]),
(RCURLY "}" [10:3]),
(RCURLY "}" [11:1])
]
Parser
query {
character {
firstName
lastName
friends {
firstName
lastName
}
}
}
GraphQL.parse(query) =>
#<GraphQL::Language::Nodes::Document:0x00007fbd8ae3dec0
@definitions=
[#<GraphQL::Language::Nodes::OperationDefinition:…
@col=1,
@directives=[],
@filename=nil,
@line=2,
@name=nil,
@operation_type="query",
@selections=
[#<GraphQL::Language::Nodes::Field:…
@alias=nil,
@arguments=[],
@col=3,
@directives=[],
@filename=nil,
@line=3,
@name="character"
Lexer & Parser
Javascript implementation of GraphQL has hand-written lexer and parser.

GraphQL Ruby uses a tool called Ragel to generate its lexer.

GraphQL Ruby uses a tool called Racc to generate its parser.
GraphQL-Ruby Phases of Execution
Tokenizing and Parsing are fundamental steps in the execution process.

Validating, Rewriting, Analyzing, Executing are just details of the implementation.
Pitfalls
🤕
Pitfalls
• Authentication

• File uploading

• N+1

• Caching

• Performance
Authentication
• One endpoint

• Handle guests and registered users

• Fetch schema definition

• Solutions:

1. auth processes with GraphQL

2. auth by tokens (JWT, OAuth) (check tokens)
File uploading
• Vanilla GraphQL doesn’t support throwing raw files into your mutations

• Solutions:

1. Base64 Encoding

2. Separate Upload Requests

3. GraphQL Multipart Requests(most recommended)
N+1
• The server executes multiple unnecessary round trips to data stores for
nested data

• Problem in how GraphQL works, not Ruby

• Solutions:

1. preload data

2. gem ‘graphql-batch’

3. gem ‘batch-loader’
Caching
• Transport-agnostic

• Uses POST with HTTP

• Don’t have url with identifier

• Solution:

1. Server side caching(Memcache, Redis, etc.)
Performance
• Big queries can bring your server down to its knees

• Solutions:

1. Set query timeout

2. Check query depth

3. Check query complexity
Links
• https://graphql.org

• https://gql.foundation

• https://graphql.github.io/graphql-spec

• https://graphql-ruby.org

• https://github.com/valikos/rm-27
Thanks
🙌
Questions?
🤔

What/How to do with GraphQL? - Valentyn Ostakh (ENG) | Ruby Meditation 27

  • 1.
    What/how to dowith GraphQL? Valentyn Ostakh #27
  • 2.
    query { me { firstName lastName company position socialLinks{ name url } } } { "data": { "me": { "firstName": "Valentyn", "lastName": "Ostakh", "company": "RubyGarage", "position": "Ruby/JS developer", "socialLinks": [ { "name": "facebook", "url": "https://facebook.com/valikos" }, { "name": "twitter", "url": "https://twitter.com/valikos_ost" }, { "name": "github", "url": "https://github.com/valikos" } ] } } }
  • 3.
    Agenda • Introduction intoGraphQL • GraphQL SDL(Schema Definition Language) • GraphQL Execution • Pitfalls
  • 4.
  • 5.
    What is GraphQL? •A query language for API • A type system of defined data • A platform of both backend + frontend applications
  • 6.
    More About GraphQL •GraphQL was developed internally by Facebook in 2012 before being publicly released in 2015 • The idea of GraphQL became from the frontend team • GraphQL services can be written in any language • GraphQL is transport-agnostic • GraphQL represents data in graphs
  • 7.
    GraphQL SDL What todo with GraphQL? 🗺
  • 8.
    GraphQL SDL GraphQL schemais at the center of any GraphQL server implementation. GraphQL schema describes the functionality available to the clients who connect to it
  • 9.
    GraphQL SDL • Schema •Object Types • Fields • Scalars • Enums • Lists • Non-Null • Directives • Aliases • Interfaces • Unions • Fragments • Input Objects
  • 10.
  • 11.
    GraphQL Object Type { "data":{ "character": { "firstName": "Peter", "lastName": "Griffin", "friends": [ { "firstName": "Brian", "lastName": null }, { "firstName": "Homer", "lastName": "Simpson" } ] } } } query { character { firstName lastName friends { firstName lastName } } }
  • 12.
    GraphQL Object Type { "data":{ "character": { "firstName": "Peter", "lastName": "Griffin", "friends": [ { "firstName": "Brian", "lastName": null }, { "firstName": "Homer", "lastName": "Simpson" } ] } } } query { character { firstName lastName friends { firstName lastName } } }
  • 13.
    GraphQL Object Type typeCharacter { firstName: String! lastName: String friends: [Character!] } module Types class CharacterType < BaseObject field :first_name, String, null: false field :last_name, String, null: true field :friends, [Types::CharacterType], null: true end end
  • 14.
  • 15.
    GraphQL Fields type Character{ firstName: String! lastName: String friends: [Character!] } module Types class CharacterType < BaseObject field :first_name, String, null: false field :last_name, String, null: true field :friends, [Types::CharacterType], null: true end end
  • 16.
    GraphQL Fields { "data": { "character":{ "firstName": "Peter", "lastName": "Griffin", "friends": [ { "firstName": "Brian", "lastName": null }, { "firstName": "Homer", "lastName": "Simpson" } ] } } } query { character { firstName lastName friends { firstName lastName } } }
  • 17.
    GraphQL Fields { "data": { "character":{ "firstName": "Peter", "lastName": "Griffin", "friends": [ { "firstName": "Brian", "lastName": null }, { "firstName": "Homer", "lastName": "Simpson" } ] } } } query { character { firstName lastName friends { firstName lastName } } }
  • 18.
  • 19.
    GraphQL Fields: Arguments { "data":{ "character": { "firstName": "Jon", "lastName": "wonS", "friends": [ { "firstName": "Samwell", "lastName": "Tarly" } ] } } } query { character { firstName lastName(reverse: true) friends(last: 1) { firstName lastName } } }
  • 20.
    GraphQL Fields: Arguments { "data":{ "character": { "firstName": "Jon", "lastName": "wonS", "friends": [ { "firstName": "Samwell", "lastName": "Tarly" } ] } } } query { character { firstName lastName(reverse: true) friends(last: 1) { firstName lastName } } }
  • 21.
    GraphQL Fields: Arguments typeCharacter { firstName: String! lastName(reverse: Boolean): String friends(last: Int): [Character!] } module Types class CharacterType < BaseObject field :first_name, String, null: false field :last_name, String, null: true do argument :reverse, Boolean, required: false end field :friends, [Types::CharacterType], null: true do argument :last, Integer, required: false end end end
  • 22.
    GraphQL Fields: Arguments typeCharacter { firstName: String! lastName(reverse: Boolean): String friends(last: Int): [Character!] } module Types class CharacterType < BaseObject field :first_name, String, null: false field :last_name, String, null: true do argument :reverse, Boolean, required: false end field :friends, [Types::CharacterType], null: true do argument :last, Integer, required: false end end end
  • 23.
    GraphQL Fields: Resolvers moduleTypes class CharacterType < BaseObject field :first_name, String, null: false field :last_name, String, null: true field :full_name, String, null: false end end
  • 24.
    GraphQL Fields: Resolvers moduleTypes class CharacterType < BaseObject field :first_name, String, null: false field :last_name, String, null: true field :full_name, String, null: false def full_name [object.full_name, object.last_name].join(' ') end end end
  • 25.
    GraphQL Fields: Resolvers moduleTypes class CharacterType < BaseObject field :first_name, String, null: false field :last_name, String, null: true field :full_name, String, null: false, resolver: Resolvers::FullName end end
  • 26.
    GraphQL Fields: Resolvers moduleResolvers class Character < Resolvers::Base type Character, null: false argument :id, ID, required: true def resolve(id:) Character.find(id) end end end
  • 27.
  • 28.
    GraphQL Scalars A GraphQLobject type has a name and fields but at some point those fields have to become some concrete data. That's where the scalar types come in: they represent the leaves of the query.
  • 29.
    GraphQL Scalars: Built-inTypes • Int: A signed 32‐bit integer • Float: A signed double-precision floating-point value • String: A UTF‐8 character sequence • Boolean: true or false • ID: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable
  • 30.
    GraphQL Scalars In mostGraphQL service implementations, there is also a way to specify custom scalar types.
  • 31.
    GraphQL Scalars: GraphQL-RubyScalar Types • Int: like a JSON or Ruby integer • Float: like a JSON or Ruby floating point decimal • String: like a JSON or Ruby string • Boolean: like a JSON or Ruby boolean (true or false) • ID: which a specialized String for representing unique object identifiers • ISO8601DateTime: an ISO 8601-encoded datetime
  • 32.
    GraphQL Scalars: CustomScalar class Types::Url < Types::BaseScalar description "A valid URL, transported as a string" def self.coerce_input(input_value, context) url = URI.parse(input_value) if url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS) url else raise GraphQL::CoercionError, "#{input_value.inspect} is not a valid URL" end end def self.coerce_result(ruby_value, context) ruby_value.to_s end end
  • 33.
    GraphQL Scalars: CustomScalar class Types::Url < Types::BaseScalar description "A valid URL, transported as a string" def self.coerce_input(input_value, context) url = URI.parse(input_value) if url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS) url else raise GraphQL::CoercionError, "#{input_value.inspect} is not a valid URL" end end def self.coerce_result(ruby_value, context) ruby_value.to_s end end
  • 34.
  • 35.
    GraphQL Enums Enumerations aresimilar to custom scalars with the limitation that their values can only be one of a pre-defined list of strings.
  • 36.
    GraphQL Enums { "data": { "character":{ "firstName": "Peter", "role": "FATHER", "friends": [ { "firstName": "Brian", "role": "PET" }, { "firstName": "Stewie", "role": "SON" } ] } } } query { character { firstName role friends { firstName role } } }
  • 37.
    GraphQL Enums enum RoleEnum{ FATHER MOTHER SON DAUGHTER DOG } class Types::RoleEnum < Types::BaseEnum value ‘FATHER' value ‘MOTHER' value ‘SON' value ‘DAUGHTER' value ‘PET' end
  • 38.
    GraphQL Enums enum RoleEnum{ FATHER MOTHER SON DAUGHTER DOG } class Types::RoleEnum < Types::BaseEnum value 'FATHER', value: 1 value 'MOTHER', value: 2 value 'SON', value: 3 value 'DAUGHTER', value: 4 value 'PET', value: 5 end
  • 39.
    GraphQL Enums enum RoleEnum{ FATHER MOTHER SON DAUGHTER DOG } class Types::RoleEnum < Types::BaseEnum value 'FATHER', value: :father value 'MOTHER', value: :mom value 'SON', value: :son value 'DAUGHTER', value: :daughter value 'PET', value: :animal end
  • 40.
  • 41.
    GraphQL Lists { "data": { "character":{ "firstName": "Peter", "lastName": "Griffin", "friends": [ { "firstName": "Brian", "lastName": null }, { "firstName": "Homer", "lastName": "Simpson" } ] } } } query { character { firstName lastName friends { firstName lastName } } }
  • 42.
    GraphQL Lists type Character{ firstName: String! lastName: String friends: [Character!] } module Types class CharacterType < BaseObject field :first_name, String, null: false field :last_name, String, null: true field :friends, [Types::CharacterType], null: true end end
  • 43.
  • 44.
    GraphQL Non-Null In theGraphQL type system all types are nullable by default. This means that a type like Int can take any integer (1, 2, etc.) or null which represents the absence of any value. However, the GraphQL type system allows you to make any type non- null which means that the type will never produce a null value. When using a non-null type there will always be a value.
  • 45.
    GraphQL Non-Null type Character{ firstName: String! lastName: String friends: [Character!]! } type Character { firstName: String! lastName: String friends: [Character]! } type Character { firstName: String! lastName: String friends: [Character!] } type Character { firstName: String! lastName: String friends: [Character] }
  • 46.
    GraphQL Non-Null module Types classCharacterType < BaseObject field :first_name, String, null: false field :last_name, String, null: true field :friends, [Types::CharacterType, null: true], null: true field :friends, [Types::CharacterType, null: true], null: false field :friends, [Types::CharacterType, null: false], null: true field :friends, [Types::CharacterType, null: false], null: false end end
  • 47.
  • 48.
    GraphQL Unions { search(in: "AdventureTime") { __typename ... on Character { firstName } ... on land { name } ... on Building { type } } } { "data": { "search": [ { "__typename": "Character", "firstName": "Finn" }, { "__typename": "Land", "name": "Land of Ooo" }, { "__typename": "Building", "type": "Fort" } ] } }
  • 49.
    GraphQL Unions union Result= Character | Land | Building type Character { firstName: String! } type Building { type: String! } type Land { name: String! } type Query { search: [Result] }
  • 50.
    GraphQL Unions union Result= Character | Land | Building type Character { firstName: String! } type Building { type: String! } type Land { name: String! } type Query { search: [Result] }
  • 51.
    GraphQL Unions class Types::ResultType< Types::BaseUnion possible_types Types::CharacterType, Types::LandType, Types::BuildingType # Optional: if this method is defined, it will override `Schema.resolve_type` def self.resolve_type(object, context) if object.is_a?(Character) Types::CharacterType elsif object.is_a?(Land) Types::LandType else Types::BuildingType end end end
  • 52.
  • 53.
    GraphQL Interfaces interface Node{ id: ID! } interface Person { firstName: String! lastName: String } type Land implements Node { id: ID! name: String! } type Character implements Node & Person { id: ID! firstName: String! lastName: String }
  • 54.
    GraphQL Interfaces module Types::Interfaces::Node includeTypes::BaseInterface field :id, ID, null: false end module Types::Interfaces::Person include Types::BaseInterface field :first_name, String, null: false field :last_name, String, null: true end module Types class CharacterType < BaseObject implements Types::Interfaces::Node implements Types::Interfaces::Person end end
  • 55.
  • 56.
    GraphQL Fragments query { character{ firstName lastName friends { firstName lastName } } }
  • 57.
    GraphQL Fragments fragment CharacterFragmenton Character { firstName lastName } query { character { ...CharacterFragment friends { ...CharacterFragment } } }
  • 58.
  • 59.
    GraphQL Aliases query { hero:character(hero: true) { firstName } antihero: character(hero: false) { firstName } } { "data": { "hero": { "firstName": "Harrison", }, "antihero": { "firstName": "Sebastian", } } }
  • 60.
  • 61.
    GraphQL Directives • @deprecated(reason:String) - marks fields as deprecated with messages • @skip(if: Boolean!) - GraphQL execution skips the field if true by not calling the resolver • @include(id: Boolean!) - Calls resolver for annotated field if true GraphQL provides several built-in directives:
  • 62.
    GraphQL Directives query { character{ firstName lastName friends @skip(if: $isAlone){ firstName lastName } } } type Character { firstName: String! lastName: String surname: String @deprecated(reason: "Use lastName. Will be removed...") friends: [Character!] }
  • 63.
    GraphQL Directives: CustomDirective class Directives::Rest < GraphQL::Schema::Directive description "..." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :url, String, required: true, description: "..." def self.include?(obj, args, ctx) # implementation end def self.resolve(obj, args, ctx) # implementation end end
  • 64.
    query { character @rest(url:"/path") { firstName lastName friends { firstName lastName } } } GraphQL Directives: Custom Directive
  • 65.
  • 66.
    GraphQL Input Objects mutationCreateCharacter( $firstName: String! $lastName: String $role: RoleEnum! ) { createCharacter( firstName: $firstName lastName: $lastName role: $role ) { firstName lastName role } } mutation CreateCharacter( $input: CharacterAttributes! ) { createCharacter(input: $input) { firstName lastName role } }
  • 67.
    GraphQL Input Objects inputCharacterAttributes { firstName: String! lastName: String role: RoleEnum! } class Types::CharacterAttributes < Types::BaseInputObject argument :first_name, String, required: true argument :last_name, String, required: false argument :role, Types::RoleEnum, required: false end
  • 68.
  • 69.
  • 70.
    GraphQL Schema schema { query:Query mutation: Mutation subscription: Subscription } class MySchema < GraphQL::Schema # Required: query Types::QueryType # Optional: mutation Types::MutationType subscription Types::SubscriptionType end
  • 71.
    GraphQL Schema class Types::QueryType< GraphQL::Schema::Object field :character, Types::CharacterType, resolver: Resolvers::Character end # Similarly: class Types::MutationType < GraphQL::Schema::Object # ... end # and class Types::SubscriptionType < GraphQL::Schema::Object # ... end type Query { character(id: ID!): CharacterType }
  • 72.
  • 73.
    What GraphQL SDLgives us? Documentation
  • 74.
    What GraphQL SDLgives us? Documentation is the first class citizen in GraphQL
  • 75.
    GraphQL Execution How todo with GraphQL? 🚀
  • 76.
    When executing aGraphQL query one of the very first things the server needs to do is transform the query (currently a string) into something it understands. Transport
  • 77.
    Transport • HTTP POST •one endpoint (e.g. /graphql) • response code 200 OK
  • 78.
  • 79.
    GraphQL-Ruby Phases ofExecution • Tokenize: splits the string into a stream of tokens • Parse: builds an abstract syntax tree (AST) out of the stream of tokens • Validate: validates the incoming AST as a valid query for the schema • Rewrite: builds a tree of nodes which express the query in a simpler way than the AST • Analyze: if there are any query analyzers, they are run • Execute: the query is traversed, resolve functions are called and the response is built • Respond: the response is returned as a Hash
  • 81.
    Tokenize & Parsing GraphQLhas its own grammar. We need this rules to split up the query.  GraphQL Ruby uses a tool called Ragel to generate its lexer. How to understand that we have valid data?
  • 82.
    How to verifyincoming data? • Breaks up a stream of characters (in our case a GraphQL query) into tokens • Turns sequences of tokens into a more abstract representation of the language
  • 83.
    Lexer query { character { firstName lastName friends{ firstName lastName } } } GraphQL.scan(query) => [ (QUERY "query" [2:1]), (LCURLY "{" [2:7]), (IDENTIFIER "character" [3:3]), (LCURLY "{" [3:13]), (IDENTIFIER "firstName" [4:5]), (IDENTIFIER "lastName" [5:5]), (IDENTIFIER "friends" [6:5]), (LCURLY "{" [6:13]), (IDENTIFIER "firstName" [7:7]), (IDENTIFIER "lastName" [8:7]), (RCURLY "}" [9:5]), (RCURLY "}" [10:3]), (RCURLY "}" [11:1]) ]
  • 84.
    Parser query { character { firstName lastName friends{ firstName lastName } } } GraphQL.parse(query) => #<GraphQL::Language::Nodes::Document:0x00007fbd8ae3dec0 @definitions= [#<GraphQL::Language::Nodes::OperationDefinition:… @col=1, @directives=[], @filename=nil, @line=2, @name=nil, @operation_type="query", @selections= [#<GraphQL::Language::Nodes::Field:… @alias=nil, @arguments=[], @col=3, @directives=[], @filename=nil, @line=3, @name="character"
  • 85.
    Lexer & Parser Javascriptimplementation of GraphQL has hand-written lexer and parser. GraphQL Ruby uses a tool called Ragel to generate its lexer. GraphQL Ruby uses a tool called Racc to generate its parser.
  • 86.
    GraphQL-Ruby Phases ofExecution Tokenizing and Parsing are fundamental steps in the execution process. Validating, Rewriting, Analyzing, Executing are just details of the implementation.
  • 87.
  • 88.
    Pitfalls • Authentication • Fileuploading • N+1 • Caching • Performance
  • 89.
    Authentication • One endpoint •Handle guests and registered users • Fetch schema definition • Solutions: 1. auth processes with GraphQL 2. auth by tokens (JWT, OAuth) (check tokens)
  • 90.
    File uploading • VanillaGraphQL doesn’t support throwing raw files into your mutations • Solutions: 1. Base64 Encoding 2. Separate Upload Requests 3. GraphQL Multipart Requests(most recommended)
  • 91.
    N+1 • The serverexecutes multiple unnecessary round trips to data stores for nested data • Problem in how GraphQL works, not Ruby • Solutions: 1. preload data 2. gem ‘graphql-batch’ 3. gem ‘batch-loader’
  • 92.
    Caching • Transport-agnostic • UsesPOST with HTTP • Don’t have url with identifier • Solution: 1. Server side caching(Memcache, Redis, etc.)
  • 93.
    Performance • Big queriescan bring your server down to its knees • Solutions: 1. Set query timeout 2. Check query depth 3. Check query complexity
  • 94.
    Links • https://graphql.org • https://gql.foundation •https://graphql.github.io/graphql-spec • https://graphql-ruby.org • https://github.com/valikos/rm-27
  • 95.
  • 96.