Understanding the GraphQL Type System
In this article, you will learn about GraphQL types: the five built-in scalar types, Enums, the List and Non-Null wrapping types, Object types, and the abstract Interface and Union types that work alongside them. You will review examples for each type and learn how to use them to build a complete GraphQL schema.
Scalar Types
All the data in a GraphQL schema ultimately resolve to various scalar types. GraphQL responses can be represented as a tree, and the scalar types are the leaves at the ends of the tree. There can be many levels in a nested response, but the last level will always resolve to a scalar (or Enum) type. GraphQL comes with five built-in scalar types:
Lets look at them in detail
Int
Int is a signed 32-bit non-fractional numerical value. It is a signed (positive or negative) integer that does not include decimals. The maximum value of a signed 32-bit integer is 2,147,483,647. This is one of the two built-in scalars used for numerical data.
Float
A Float is a signed double-precision fractional value. It is a signed (positive or negative) number that contains a decimal point, such as 1.4. This is the other built-in scalar used for numerical data.
String
A String is a UTF-8 character sequence. The String type is used for any textual data. This can also include data like very large numbers. Most custom scalars will be types of string data.
Boolean
A Boolean is a true or false value.
ID
An ID is a unique identifier. This value is always serialized as a string, even if the ID is numerical. An ID type might be commonly represented with a Universally Unique Identifier (UUID).
Custom Scalars
In addition to these built-in scalars, the scalar keyword can be used to define a custom scalar. You can use custom scalars to create types that have additional server-level validation, such as Date, Time, or Url. Here is an example defining a new Date type
scalar Date
The server will know how to handle interactions with this new type using the GraphQLScalarType.
Enum Type
The Enum type, also known as an Enumerator type, describes a set of possible values.
Enum types are a special kind of scalar that is restricted to a particular set of allowed values. This allows you to:
Here's what an enum definition might look like in the GraphQL schema language:
"The job class of the RangerNature."
enum Job {
DOCTOR
WIZARD
}
"The Species or what type of the RangerNature."
enum Species {
HUMAN
ALIEN
DWARF
}
In this way, it is guaranteed that the Job of a ranger nature is DOCTOR or WIZARD and can never accidentally be "purple" or some other random string, which could be possible if you used a String type instead of making a custom Enum. Enums are written in all-caps by convention.
Enums can also be used as the accepted values in arguments. For example, you might make a USAGE enum to denote whether a weapon is single-handed (like a short sword) or double-handed (like a strong axe), and use that to determine whether one or two can be equipped:
enum USAGE {
SINGLE
DOUBLE
}
"A valiant weapon wielded by a fighter."
type Weapon {
name: String!
attack: Int
range: Int
hand: Hand
}
type Query {
weapons(usage: USAGE = SINGLE): [Weapon]
}
The USAGE enum has been declared with SINGLE and DOUBLE as values, and the argument on the weapons field has a default value of SINGLE, meaning if no argument is passed then it will fall back to SINGLE.
Non-Null Type
You might notice that null or undefined, a common type that many languages consider a primitive, is missing from the list of built-in scalars. Null does exist in GraphQL and represents the lack of a value.
All types in GraphQL are nullable by default and therefore null is a valid response for any type. In order to make a value required, it must be converted to a GraphQL NON-NULL type with a trailing exclamation point (!). Non-Null is defined as a type modifier, which are types used to modify the type it is referring to. As an example, String is an optional (or nullable) string, and String! is a required (or Non-Null) string.
List Type
A List type in GraphQL is another type modifier. Any type that is wrapped in square brackets ([]) becomes a List type, which is a collection that defines the type of each item in a list.
As an example, a type defined as [Int] will be a collection of Int types, and [String] will be a collection of String types. Non-Null and List can be used together to make a type both required and defined as a List, such as [String]!.
Object Type
If GraphQL scalar types describe the “leaves” at the last of the ladder.GraphQL response, then Objects types describe the median “branches”, and almost everything in a GraphQL schema is a type of Object.
Objects consist of a list of named fields (keys) and the value type that each field will resolve to. Objects are defined with the type keyword. At least one or more fields must be defined, and fields cannot begin with two underscores (__) to avoid conflict with the GraphQL introspection system.
"A Power ranger who saves world with his strong power and sharp mind."
type PowerRanger {
id: ID!
age: Int
org: String!
active: Boolean!
name: String
friends:[String]
}
In this example, the PowerRanger Object type has been declared, and it has has four named fields:
Above the declaration, you can also add a comment using double quotes, as in this example: "A Power ranger who saves world with his strong power and sharp mind.". This will appear as the description for the type.
In this example, each field resolves to a scalar type, but Object fields can also resolve to other Object types. For example, you could create a Weapon type, and the GraphQL schema can be set up where the weapon field on the PowerRanger will resolve to a Weapon Object
"A valiant weapon powered by a PowerRanger."
type Weapon {
name: String!
attack: Int
range: Int
}
"A Power ranger who saves world with his strong power and sharp mind."
type PowerRanger {
id: ID!
age: Int
org: String!
active: Boolean!
name: String
weapon: Weapon
friends:[String]
}
Interface Type
As other languages offer GraphQL also supports interfaces. Like the Object type, the abstract Interface type consists of a list of named fields and their associated value types. Interfaces look like and follow all the same rules as Objects but are used to define a subset of an Object’s implementation.
So far in our schema, we have a PowerRanger Object, but you might also want to make a Doctor, a Healer, and other Objects that will share many of the same fields but have a few differences. In this case, you can use an Interface to define the fields they all have in common, and create Objects that are implementations of the Interface.
In the following example, you could create a Ranger Interface using the interface keyword with all the fields every type of ranger will possess
"A ranger on request ."
interface Ranger {
id: ID!
name: String!
org: String!
age: Int!
active: Boolean!
}
Every ranger type will have the fields id, name, org, age, and active.
Now, imagine you have a PowerRanger type and a Wizard type that have these shared fields, but PowerRangers use a Weapon and Wizards use Spells. You can use the implements keyword to delineate each as a Ranger implementation, which means they must have all the fields from the created Interface:
"A Power ranger who saves world with his strong power and sharp mind."
type PowerRanger {
id: ID!
age: Int
org: String!
active: Boolean!
name: String
friends:[String]
}
"A Ranger with a variety of powers."
type Wizard implements Ranger{
id: ID!
name: String!
org: String!
age: Int!
active: Boolean!
spells: [Spell]
}
PowerRanger and Wizard are both valid implementations of the Ranger Interface because they have the required subset of fields.
Union Type
Another abstract type that can be used with Objects is the Union type. Using the union keyword, you can define a type with a list of Objects that are all valid as responses.
Using the Interfaces created in the previous section, you can create a RangerNature Union that defines a nature as a Wizard OR a PowerRanger
union RangerNature = Wizard | Fighter
The equal character (=) sets the definition, and the pipe character (|) functions as the OR statement. Note that a Union must consist of Objects or Interfaces. Scalar types are not valid on a Union.
Now if you query for a list of characters, it could use the RangerNature Union and return all Wizard and PowerRanger types.