GraphQL Types system
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
{user {namebook: {titlepages}}}
{"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
- Built-in default scalars
- 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
orfalse
- 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
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
enum Gender {MASCULINEFEMININECOMMONNEUTER}input createUserInput { # later on this input object typesname: 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
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
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
interface Book {title: Stringauthor: Author}type TextBook implements Book {title: String # Must be presentauthor: Author # Must be presentcourse: Course}type Comic implements Book {title: String # Must be presentauthor: Author # Must be presentgenre: 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.
query {myBooks {title # always present part of Book interfaceauthor # always present part of Book interface...on TextBook {course: { # only present on type TextBookname}}...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
type Movie {name: Stringdirector: String}union SearchResult = Book | Moviequery {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
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
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
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.
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
# 1. List of Non-Null typesnickNames: [String!]# This means the list nickNames can itself be null but cant have null members i.enickNames: [] # validnickNames: null # validnickNames: ["Bruno", "B" ] # validnickNames: ["Bruno", null] # not valid error# -----------# 2. Non-Null List of typesnickNames: [String]!# This means the list nickNames can itself not be null but can have null members and can be empty i.enickNames: null # not valid errornickNames: [] # validnickNames: ["Bruno", "B" ] # validnickNames: ["Bruno", null] # valid# -----------# 3. Non-Null List of Non-Null typesnickNames: [String!]!# This means the list nickNames can itself be null but cant have null members i.enickNames: null # not valid errornickNames: [] # validnickNames: ["Bruno", "B" ] # validnickNames: ["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