GraphQL SDL: GraphQL Schema Fields & Types - I #4

A brief introduction to GraphQL types such as Lists, Fields & Objects, Emus and more


 Image Credits: https://www.graph.cool/
Image Credits: https://www.graph.cool/

In this post, you'll learn about:
  • Scalar Types
  • Objects & Fields
  • Enumeration types
  • Arguments
  • Lists Types and Non-Null
  • Union Types


Scalar types

A GraphQL object type has a name and fields, but at some point, those fields have to resolve some concrete data. That's where the scalar types come in: they represent the leaves of the query.
type Person { id: Int edited: String name: String created: String gender: String skin_color: String hair_color: String height: Int eye_color: String mass: Int birth_year: String image: String
}

GraphQL comes with a set of default scalar types out of the box:
  • 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.

Below is the GraphQL Query to retrieve Person data with the id:
{
person (id: 1) {
id
edited
name
created
gender
skin_color
hair_color
height
eye_color
mass
birth_year
image
}
}


In most GraphQL service implementations, there is also a way to specify custom scalar types. For example, we could define a Date type:
scalar Date

Then it's up to our implementation to define how that type should be serialized, deserialized, and validated. For example, you could specify that the Date type should always be serialized into an integer timestamp, and your client should know to expect that format for any date fields.
. . .

Object types and fields

The most basic components of a GraphQL schema are object types, which just represent a kind of object you can fetch from your service, and what fields it has.

In the GraphQL schema language, we might represent it like this:
type Starship { films: [Film!]! name: String! }

type Film { title: String! producer: String! }

The language is pretty readable, but let's go over it so that we can have a shared vocabulary:
  • Starship is a GraphQL Object Type, meaning it's a type with some fields. Most of the types in your schema will be object types.
  • name and films are fields on the Starship type. That means that name and films are the only fields that can appear in any part of a GraphQL query that operates on the Character type.
  • String is one of the built-in scalar types - these are types that resolve to a single scalar object, and can't have sub-selections in the query. We'll go over scalar types more later.
  • String! means that the field is non-nullable, meaning that the GraphQL service promises to always give you a value when you query this field. In the type language, we'll represent those with an exclamation mark.
  • [Film!]! represents an array of Film objects. Since it is also non-nullable, you can always expect an array (with zero or more items) when you query the film field. And since Film! is also non-nullable, you can always expect every item of the array to be a Film object.
Nested GraphQL fields & objects can be retrieved with the following graphql query:
{
starship(id: 2) {
name
films {
producer
title
}
}
}


Now you know what a GraphQL object type looks like, and how to read the basics of the GraphQL type language.
. . .

Enumeration types

Also called Enums, enumeration types are a special kind of scalar that is restricted to a particular set of allowed values. This allows you to:
  1. Validate that any arguments of this type are one of the allowed values
  2. Communicate through the type system that a field will always be one of a finite set of values

Here's what an enum definition might look like in the GraphQL schema language:
enum Episode { NEWHOPE EMPIRE JEDI }

type Person { name: String! appearsIn: [Episode]! }

This means that wherever we use the type Episode in our schema, we expect it to be exactly one of NEWHOPE, EMPIRE, or JEDI.

Note that GraphQL service implementations in various languages will have their own language-specific way to deal with enums. In languages that support enums as a first-class citizen, the implementation might take advantage of that; in a language like JavaScript with no enum support, these values might be internally mapped to a set of integers. However, these details don't leak out to the client, which can operate entirely in terms of the string names of the enum values.
. . .

Arguments

Every field on a GraphQL object type can have zero or more arguments, for example, the length field below:
type Starship { id: ID! name: String! length(unit: LengthUnit = METER): Float }

All arguments are named. Unlike languages like JavaScript and Python where functions take a list of ordered arguments, all arguments in GraphQL are passed by name specifically. In this case, the length field has one defined argument, unit.

Arguments can be either required or optional. When an argument is optional, we can define a default value - if the unit argument is not passed, it will be set to METER by default.
. . .

Lists Types and Non-Null

Object types, scalars, and enums are the only kinds of types you can define in GraphQL. But when you use the types in other parts of the schema, or in your query variable declarations, you can apply additional type modifiers that affect validation of those values. Let's look at an example:
type Starship { films: [Film!]! name: String! }

Here, we're using a String type and marking it as Non-Null by adding an exclamation mark, ! after the type name. This means that our server always expects to return a non-null value for this field, and if it ends up getting a null value that will actually trigger a GraphQL execution error, letting the client know that something has gone wrong.

Below query retrieves the list of movies in which starship is appeared, here films is a list of Film type :
{ starship(id: 2) { name films { producer title } } }


The Non-Null type modifier can also be used when defining arguments for a field, which will cause the GraphQL server to return a validation error if a null value is passed as that argument, whether in the GraphQL string or in the variables.
query planet($id: Int!) { // here id is required & cant be null Planet(id: $id) { id: Int climate: String surface_water: String name: String diameter: Int } }
In below GraphQL query, Id is required, it will throw an error if Id is missing in the query:
{
planet (id: 1) {
id
name
diameter
climate
surface_water
}
}

Lists work in a similar way: We can use a type modifier to mark a type as a List, which indicates that this field will return an array of that type. In the schema language, this is denoted by wrapping the type in square brackets, [ and ]. It works the same for arguments, where the validation step will expect an array for that value.

The Non-Null and List modifiers can be combined. For example, you can have a List of Non-Null Strings:
myField: [String!]

This means that the list itself can be null, but it can't have any null members. For example, in JSON:
myField: null // valid myField: [] // valid myField: ['a', 'b'] // valid myField: ['a', null, 'b'] // error

Now, let's say we defined a Non-Null List of Strings:
myField: [String]!

This means that the list itself cannot be null, but it can contain null values:
myField: null // error myField: [] // valid myField: ['a', 'b'] // valid myField: ['a', null, 'b'] // valid

You can arbitrarily nest any number of Non-Null and List modifiers, according to your needs.
. . .

Union types

Union types are very similar to interfaces, but they don't get to specify any common fields between the types.
union SearchResult = Person| Vehicle | Starship

Wherever we return a SearchResult type in our schema, we might get a Person, a Vehicle, or a Starship. Note that members of a union type need to be concrete object types; you can't create a union type out of interfaces or other unions.

In this case, if you query a field that returns the SearchResult union type, you need to use a conditional fragment to be able to query any fields at all:
{
search (search: "an") {
__typename
... on Person {
name
height
birth_year
}
... on Vehicle {
name
model
}
... on Starship {
name
passengers
}
}
}

The __typename field resolves to a String which lets you differentiate different data types from each other on the client
. . .
In the next blog, we will look into the Input types, Query, Mutation & Subscription types.


On a mission to build Next-Gen Community Platform for Developers