GraphQL Types system

Strict policy for better control

GraphQL Types systemHeader Image

Type systems are built in feature of most programming languages, Their implementations differ from language to languages and have occurrence to type check at compile-time or at run-time, may be manually annotated or system inferred. Type systems are part of long time academic study called Type theory. The fundamental problem that type theory addresses is that to ensure that programs have meaning, assigning a data type also termed as typings gives meaning to a sequence of bits, gives it rules to live by.

According to Wikipedia

In programming languages, a type system is a logical system comprising a set of rules that assigns a property called a type to the various constructs of a computer program. The main purpose of a type system is to reduce possibilities for bugs in computer programs by defining interfaces between different parts of a computer program, and then checking that the parts have been connected in a consistent way.

Type systems in GraphQL

As described in my article on Why GraphQL?, GraphQL query closely matches with the result that the query returns, So it much important and useful to have exact description of data we can ask, select and what kind of data it returns. Every GraphQL service, each level of query and fields corresponds to a particular type and each type describes a set of available fields. Since GraphQL service can be written in any language and can't specifically rely on language implementations to talk about GraphQL's schema and types so, GraphQL comes with its own simple schema language called as "GraphQL schema language" similar to its query language allows GraphQL to talk about schemas and types in a language-agnostic way.

This type system is already a great advantage to API's compared to REST API, Let's take a ride into GraphQL type system in depth. There are different kinds of types in GraphQL and these kinds are classified as

  • Scalar
  • Object
  • Input Object
  • Union
  • Interfaces
  • List
  • Non-Null

All these above types are defined as enumeration(enum) __TypeKind in GraphQL SDL(Schema Definition Language). One can speculate about Type Kinds in GraphQL specs

Schema type and Root types

When creating a GraphQL server we need to first initialize it with our GraphQL schema, This Schema type serves as a first entry point to our whole schema. The Root type defines all the operations that are made available in the GraphQL server. The operations are Query, Mutation and Subscription. Most of the types in our schema are normal objects while a GraphQL service has to have a query type and may of may not have mutation and subscription types. These types are also same as a regular types but special in a way that they define entry point to every GraphQL query, mutation and subscription types

Scalars and Enumeration types

Scalars and enums are primitive values in GraphQL. What it means is that the GraphQL objects, fields and everything in GraphQL has to resolve to something that is concrete and that's what are scalars. As the specs says they represent the leaves of the query

scalars

Consider an example below

graphql
{
user {
name
book: {
title
pages
}
}
}
json
{
"data": {
"user": {
"name": "Jay",
"book": {
"title": "Some book title",
"pages": 200
}
}
}
}

In the above query name, title and pages are fields that resolve to scalar types as they do not have any sub-fields and so represent the leaves of the query. book is a object type that resolves into fields that ultimately resolve into scalars.

In GraphQL there are two types of scalars

  1. Built-in default scalars
  2. custom scalars or user-defined scalars

There are 5 built-in/default scalars

  • Int: Signed 32 bit integer
  • Float: Signed double-precision floating point
  • String: A UTF-8 character sequence
  • Boolean: true or false
  • ID: A scalar type that represents unique identifier, is represented the same way as string but is intended to be not human readable, often used as a key for cache or objects

All data cannot be represented by these default types so For better representation of such data GraphQL allows to specify custom scalar types for example

graphql
scalar DateTime

Now, the implementation of our custom scalar and how this is serialized, deserialized, and validated is up to us.

kind of this type must return __TypeKind.SCALAR

Enumeration types

Enumeration types also called as enums , these are special kind of scalar that is restricted to a particular set of allowed values. Enums can also be used as input and output type objects. This allows us to

  • validate that any field of this type are one of those allowed values specified by that enum.
  • States through type system that a field will always be one of a finite set of values

Lets see how enums are implemented using GraphQL SDL

graphql
enum Gender {
MASCULINE
FEMININE
COMMON
NEUTER
}
input createUserInput { # later on this input object types
name: String!
email: String!
gender: Gender!
}
query {
createUser(input: createUserInput!): User!
}

This means wherever we use the type Gender in our schema, we expect it to be exactly one of the for values(i.e MASCULINE, FEMININE, COMMON, NEUTER), so our gender field in createUserInput input argument should always be one of those allowed values.

Object types and Input object

Objects are most commonly used types of GraphQL schema. we can see in our above example that book field might be represented as object type as follows

graphql
type Book {
title: String!
pages: Int!
}

Book is a GraphQL object type meaning its a type with some fields. title and pages are fields on Book type which eventually resolve into scalar type of String and Int. The exclamation mark on the end of the scalar type is a modifier which represents a non-nullable field, means a GraphQL service promises return you a value of promised type when you query that field.

GraphQL Object type are categorized into two types

  • Input types
  • Output types

Lets look at what GraphQL specification has to say about why Objects are categorized into these separate types

Objects can contain fields that express circular references or references to interfaces and unions, neither of which is appropriate for use as an input argument. For this reason, input objects have their own separate type in the system.

input types are defined as follows

graphql
input createBookInput {
title: String!
pages: Int!
}
mutation createBook(input: createBookInput!): Book!

I find it a good practice to use Action-type + ObjectThatIsToBeEffected + Input(createBookInput) convention for naming inputs

As one can see in our above example createBookInput is Input object type and Book is Output object type which defines the output of our createBook mutation. More on mutation later in this blog. This way we are returning out Book after creating it and maybe persisting it in our database. so, we're no also able to efficiently update our cache on the client side. Its generally good practice to return our mutated data(that is created, deleted or updated) after mutation. Its important to notice that we have used a non-null(!) modifier for input. So, if the input argument some turns out to be passed as null then our GraphQL service errors out.

Interfaces and Unions

GraphQL specification mentions two abstract types namely

  • Interfaces
  • Unions

Below we'll look at why abstract types are used and how to implement them. Abstract types greatly improve GraphQL schema and simplify queries and mutations

Interfaces

An Interface is an abstract type that includes a certain set of fields that a type must include to implement the interface.

As said by the specification above Interfaces are usually needed when we are looking to access certain fields of objects that have to comply to the properties defined by the interface. These set of fields can be included by multiple object types. for example consider the below example we can abstract out the type Book to interface as follows

graphql
interface Book {
title: String
author: Author
}
type TextBook implements Book {
title: String # Must be present
author: Author # Must be present
course: Course
}
type Comic implements Book {
title: String # Must be present
author: Author # Must be present
genre: String
}
type Query {
myBooks: [Book] # list of Book
}

As we can see above every type that implements our interface Book also have the same fields in common(title, author). These fields also need to be explicitly written for each type implementing the interface. Now if you see this gives us one greatest advantage to use interface is that it allows us to access a group of types in one single entity leading to much cleaner schema design, as well as reducing the complexity

In the above example Query.myBooks returns a list that include both TextBook and Comic as well. lets look at how clients can query on fields and subfields that are and are not included in interface.

graphql
query {
myBooks {
title # always present part of Book interface
author # always present part of Book interface
...on TextBook {
course: { # only present on type TextBook
name
}
}
...on Comic {
genre # only present on type Comic
}
}
}

This query uses inline fragments to fetch a Book's course if it is TextBook and genre if it is of type Comic.

Unions

Above we saw what interfaces are, how we can abstract out the common field on different types. what if we wish to apply similar kind of abstraction to type that do not have any common fields? For Book interface for example, we assumed that for every type that implement interface Book have title and author in common. But lets consider two types that have no fields in common and will not wish to add any such constraints on children types. Unions are great to represent such types, We just have to represent multiple types that do not have any fields in common. Lets look at a example

graphql
type Movie {
name: String
director: String
}
union SearchResult = Book | Movie
query {
search(contains: String!): [SearchResult]
}

The SearchResults union enables Query.search to return a list that includes both Book and Movie types, So clients can again make use of inline fragments to query fields on union as below

graphql
query {
search(contains: "Godfather") {
... on Book {
title
}
... on Movie {
name
}
}
}

Interfaces and union types are the only abstract types available in GraphQL, these types can be wisely used to reduce the complexity of our schema, number of queries and mutations and also describe data much better in a precise way. This issue on GitHub might be helpful to further clarify the difference between Interface and Unions and how they are meant for different use cases.

Modifiers

As we already know by now that Object types, scalars and enums are the only kind of types that we can define in GraphQL, When we use those types in our schema or during input type declaration or so, we can also apply additional type modifiers that affect validation of those values. In simple a Modifier modifies the type to which they refer to allowing use to add validations to that type. There are two type of modifiers in GraphQL they are

  • List
  • Non-Null

List

A GraphQL list is a special collection type which declares the type of each item in the List of specified type. List values are serialized as ordered lists. GraphQL list represent a collection of values, we can relate these to arrays, However this analogy is not completely precise. List modifiers are represented through a pair of squared brackets wrapped around the instance of some type. for example as we defined above

graphql
type Query {
myBooks: [Book] # list of Book
}

we specified that the query myBooks will return a list of Books, with each book of type Book. This is exactly how list modifiers can be used to modify output type to return a list of particular object type.

Non-Null

Non-Null allows us to add additional type of validation. In GraphQL SDL it is represented by ! (exclamation mark) after the type name. By marking it as Non-Null we are guaranteeing that our server always expects Non-Null value for this field, And some how if this type ends up as null value then that will throw up an execution error, letting client know that something is not right!.

for example as shown above

graphql
type Book {
title: String!
pages: Int!
}

The above example states that title and pages cannot be null and a type is also assigned, it represents that the field title cannot be null and cannot be of type other than String thus giving us strict validations on fields.

Non-Null type modifier can also be used with input types, which will cause GraphQL to return a validation error if null is passed as that input argument.

graphql
input createBookInput {
title: String!
pages: Int!
}
mutation createBook(input: createBookInput!): Book!

If any of field in the above input argument to the createBook provided by the client turns out to be null then our GraphQL service error out.

Composing Modifiers

We can also compose our Non-Null modifiers with List modifiers to create a special type modifiers with combining validation powers from both types. This gives pretty powerful validations, lets look at an example below

graphql
# 1. List of Non-Null types
nickNames: [String!]
# This means the list nickNames can itself be null but can't have null members i.e
nickNames: [] # valid
nickNames: null # valid
nickNames: ["Bruno", "B" ] # valid
nickNames: ["Bruno", null] # not valid, error!
# -----------
# 2. Non-Null List of types
nickNames: [String]!
# This means the list nickNames cannot not be null but can have null members .e
nickNames: null # not valid error
nickNames: [] # valid
nickNames: ["Bruno", "B" ] # valid
nickNames: ["Bruno", null] # valid
# -----------
# 3. Non-Null List of Non-Null types
nickNames: [String!]!
# This means the list as well as it's members cannot be null i.e
nickNames: null # not valid, error!
nickNames: [] # valid
nickNames: ["Bruno", "B" ] # valid
nickNames: ["Bruno", null] # not valid, error!

As seen above with modifiers we are able to add special behaviors to our GraphQL types and schemas. Modifiers are a great tool to make strict and elegant GraphQL schemas.

📚 Further learning resources