GraphQL - Anti Patterns in Schema design

@vilvaathibanpb

- Vilva athiban p b

About me

JavaScript / GraphQL  earns my bread

 

Travel and painting satisfies my thirst

 

Endless love for OSS but not committed to a single project for long 😉

 

Works @ Omio, A Search Engine for Travel

 

A Youtube series "Known Unknowns" - Vilva Athiban (Channel name)

@vilvaathibanpb

Let's begin

@vilvaathibanpb

what is anti patterN ?

1. A commonly used process, structure, or pattern of action that despite initially appearing to be an appropriate and effective response to a problem, has more bad consequences than good ones.
 

2. Another solution exists that is documented, repeatable, and proven to be effective.

@vilvaathibanpb

anti patters in schema design

  • Nullable Fields
  • Miscommunication in the Docs
  • Lengthy arguments to Mutation
  • Insufficient Mutation Response
  • Allowing invalid inputs
  • Schema - Circular Reference
  • Massive data in response

Nullable Fields


#Sample Schema


type Passenger {
	name: Name
  	age: Int
  	address: String
}

type Name {
	firstName: String
	secondName: String
}


<p>passenger.name.firstName</p> 
#Breaks the UI without any errors during query

#Sample Schema


type Passenger {
	name: Name!
  	age: Int
  	address: String
}

type Name {
	firstName: String
	secondName: String
}


<p>passenger.name.firstName</p> 
# Throws error when queried 
# and doesnt break the UI

Miscommunication in the Docs

# Sample schema


type Passenger {
  id: ID!
  name: String!
  age: Int!
  address: String!
  passengerId: ID!
}
# A single line, type-level description

"Passenger details"
type Passenger {

  """  a multi-line description
  the id is general user id """
  id: ID!
  name: String!
  age: Int!
  address: String!
  
  "single line description: it is passenger id"
  passengerId: ID!
  
}

Lengthy arguments to Mutation


type MutationResponse { 
  status: String!
}

type Mutation {
  createPassenger(name: String!, age: String!, address: String!): MutationResponse
}



# Mutation in Frontend looks like:
mutation PassengerMutation($name: String!, $age: String!, $address: String! ) {
  createPassenger(name: $name, age: $age, address: $address ) { 
    status
  }
}

type MutationResponse { 
  status: String!
}

type Mutation {
  createPassenger(name: String!, age: String!, address: String!, city: String!): MutationResponse
}



# Mutation in Frontend looks like:
mutation PassengerMutation($name: String!, $age: String!, $address: String!, city: String! ) {
  createPassenger(name: $name, age: $age, address: $address, city: $city ) { 
    status
  }
}

type MutationResponse { 
  status: String!
}

type PassengerData {
  name: String! 
  age: String! 
  address: String!
}

type Mutation {
  createPassenger(passenger: PassengerData!): MutationResponse
}


#Mutation in Frontend looks like:

mutation PassengerMutation($passenger: PassengerData! ) {
  createPassenger(passenger: $passenger) { 
    status
  }
}

type MutationResponse { 
  status: String!
}

type PassengerData {
  name: String! 
  age: String! 
  address: String!
  city: String!
}

type Mutation {
  createPassenger(passenger: PassengerData!): MutationResponse
}


#Mutation in Frontend looks like:

mutation PassengerMutation($passenger: PassengerData! ) {
  createPassenger(passenger: $passenger) { 
    status
  }
}

Insufficient Mutation Response


type MutationResponse { 
  status: String!
}

type Passenger {
  name: String! 
  age: String! 
  address: String!
}

type Mutation {
  createPassenger(passenger: Passenger!): MutationResponse
}

# Mutation in Frontend looks like:
mutation PassengerMutation($passenger: PassengerData! ) {
  createPassenger(passenger: $passenger) { 
    status
  }
}

type MutationResponse { 
  status: String!
  id: ID!
  updatedPassenger: Passenger
}

type Passenger {
  name: String! 
  age: String! 
  address: String!
}

type Mutation {
  createPassenger(passenger: Passenger!): MutationResponse
}

# Mutation in Frontend looks like:
mutation PassengerMutation($passenger: PassengerData! ) {
  createPassenger(passenger: $passenger) { 
    status
    id
    updatedPassenger
  }
}

Allowing invalid inputs

  • Custom Scalars

  • Enums

Custom Scalars

Allowed scalars

  • Int
  • Float
  • String
  • Boolean
  • ID

// Logic implementation for the Date type
import { GraphQLScalarType } from 'graphql';
import { Kind } from 'graphql/language';

const resolverMap = {
  Date: new GraphQLScalarType({
    name: 'Date',
    description: 'Date custom scalar type',
    parseValue(value) {
      return new Date(value); // value from the client
    },
    serialize(value) {
      return value.getTime(); // value sent to the client
    },
  }),
};
scalar Date

type MyType {
   created: Date
}

ENUMS


# Enum of allowed countries: 
enum AllowedCountry {
  Germany
  USA
  Sweden
  Denmark
}

type Passenger {
  name: String!
  age: Int!
  country: AllowedCountry! 
  #Country accepts only one of the values from the enum
}

Schema - Circular Reference


type Passenger {
  name: String!
  location: Location!
}

type Location {
  country: String!
  passenger: Passenger!
}
query getPassenger {
  name
  location {
    counrty
    passenger {
      name 
      location {
        country 
        passenger {
          name
          location {
            country
          }
        }
      }
    }
  }
} 

Backup - graphql-depth-limit

Massive data in response


type Passenger {
  name: String!
  age: Int!
}

type Query {
  getAllPassenger: [Passenger!]!
}

type Passenger {
  name: String!
  age: Int!
}

type Query {
  getAllPassenger(limit: Int): [Passenger!]!
}

type Passenger {
  name: String!
  age: Int!
}

type Query {
  # a limit of 10 makes sure, when a limit is not passed, 
  # it doesnt send more than 10 values
  getAllPassenger(limit: Int = 10): [Passenger!]!
}

thank you

QA Time: 

@vilvaathibanpb

follow me on twitter

Please drop in some suggestions and feedback, so I can get better next time 

GraphQL: Anti Patterns in Schema design

By Vilva Athiban

GraphQL: Anti Patterns in Schema design

  • 491