Easily restore your project to a previous version with our new Instant One-click Backup Recovery

GraphQL

Schemas and Types

Let's take a look into what GraphQL Schemas and Types are.

We learned about “What is GraphQL” in the previous article of this series. Hygraph is a data platform that provides you with Instant GraphQL APIs; all you have to do is define your schema in the Hygraph dashboard and you’re good to go to use the instant GraphQL APIs. However, many other things happen under the hood, which eventually scaffolds those APIs for you like defining the GraphQL schema, creating resolvers for your queries, mutations, and subscriptions, etc. In this article, we will be covering the basics of GraphQL Schemas and Types.

What is a GraphQL Schema?

A GraphQL server acts as a bridge between the client and the underlying data sources, providing a flexible and efficient way to query and manipulate data in a structured manner. When a client sends a GraphQL request to a server, the server receives the request and uses the GraphQL Schema to validate and execute the request. The server then sends back a response to the client which contains the requested data or errors, depending on the nature of the request. In order to serve these requests, and expose what operations are available to query or mutate the data, a GraphQL Server relies heavily on its Schema.

A GraphQL Type defines your entities with their fields and definitions.

For example:

type User {
name: String
email: String
}

This is a User Type, which defines two fields name and email, both are of the type String. Just like programming concepts both String and User are Types. String is a scalar type, whereas User is an object type created by us.

A GraphQL Schema is composed of various GraphQL types that define all your data entities, what are the relationships between these entities, and what operations are available to query and mutate your data. In order to support this GraphQL defines a human-readable Schema Definition Language (SDL).

For example:

type User {
name: String
email: String
posts: [Post]
}
type Post {
title: String
description: String
user: User
}

The above GraphQL schema gives us an exact picture of how User and Post entities are connected. We can clearly see a One-To-Many relationship between User-Post (one user can have many posts ) from the schema definition language itself.

GraphQL Type System

As discussed above, GraphQL defines various types that we can utilize to build our schema. Here are the different available types.

  • Scalar Type
  • Object Type
  • Input Types
  • Enumeration Type
  • Union and Interface Type
  • Lists and Non-Null

Scalar Type

Scalar types represent primitive data types for fields like Strings, Integers, etc. These types cannot have further nested sub-fields. Scalar types are automatically serialized and deserialized by GraphQL according to their respective types in the programming language.

Here are the Scalar types supported by GraphQL:

  • 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 and is serialized as a string.

Object Type

The majority of types that we define in a GraphQL schema are Object Types. Object types contain various fields and each field has its own type.

For example:

type User {
name: String
email: String
}

In this example: User is a GraphQL Object Type, meaning it's a type with some fields. Object types will make up most of your schema.

  • name and email are fields on the User type. This also means that one cannot query anything beyond - - name and email under a User type.
  • name and email fields are of the Scalar Type: String

Query & Mutation Type

GraphQL provides a Query type that can be used whenever you want to query data from your data source.

For example:

type Query {
getAllUsers: [User]
}

This GraphQL schema defines a Query type that has a single field named getAllUsers. The getAllUsers query outputs an array of User objects. This means that when a client sends a query of getAllUsers to the server, they will receive a list of all the users in the system. With a REST-based API, this would look something like GET /api/users.

The Query type defines all entry points for read operations, similarly, there is a Mutation type that defines entry points for write operations on your GraphQLl server.

For example:

type Mutation {
addUser(name: String, email: String): User
}

This GraphQL schema defines a Mutation type that has a single field named addUser. The addUser field takes two arguments: name and email, both of which are of the String type. When a client calls this mutation field with valid name and email values, a new User object is created with the provided data and added to the system. The addUser mutation returns the newly created User object.

We have only explored how queries and mutations are defined in the GraphQL schema, we shall be exploring more details of using queries and mutations in applications in the upcoming articles.

Input Type

Input types are types that will define the data type of arguments in your queries and mutations. Input types are the same as Object types, the only difference is that to define an input type we use the keyword input instead of type.

Input types are very useful when you want to pass objects as arguments to your query or mutation instead of scalars. For instance in the addUser mutation above if you want to pass a user object as an argument, then you can define your input type and mutation like this:

input UserInput {
name: String
email: String
}
type Mutation {
addUser(newUser: UserInput): User
}

One important thing to note is you cannot mix your input and output types. So if you have a query that outputs a user, you cannot reuse the above input type over there.

The example below is invalid:

type Query {
getUserById(id: ID): UserInput
}

The example below is the correct way:

input UserInput {
name: String
email: String
}
type User {
name: String
email: String
}
type Mutation {
addUser(newUser: UserInput): User
}
type Query {
getUserById(id: ID): User
}

Enumeration Type

An enum is similar to a scalar type, but all its valid values are defined in the schema itself. Enums are most useful in situations where the user must pick from a prescribed list of options.

For example:

enum GENDER {
MALE
FEMALE
OTHER
}

Lists and Non-Null

You will be using the object, scalar, input, and enum types to define your schema. GraphQL also provides us with modifiers that can help us to do quick validations. These modifiers can be used in type definitions inside the schema and also in the arguments of queries and mutations. The modifiers are listed below

Exclamation Mark ! - Non-Null Square Brackets [ ] - List

For example:

type User {
email: String!
city: String
hobbies: [String]
}

Here, we are saying that a User type will have fields email, city, and posts; an email should always exist for a user (as there is an exclamation mark in its declaration), but the city field is optional so it may or may not exist. This means that our server always expects to return a non-null value for email, If somehow, our server ends up getting a null value for the email field, it will trigger a GraphQL execution error, and let the client know that something has gone wrong.

If you take a look at the hobbies field its output is String wrapped with square brackets - [String], meaning that this field will output an array of Strings.

You can also combine the ! and [] modifiers as per your needs, here are some tricky examples:

type User {
...
hobbies: [String!]
# This means that the hobbies field can be null but it cannot have any null values
# hobbies: null // valid
# hobbies: [] // valid
# hobbies: ['cricket', 'movies'] // valid
# hobbies: ['cricket', null, 'movies'] // error
}
type User {
...
hobbies: [String]!
# This means that the hobbies field cannot be null but it can have null values
# hobbies: null // error
# hobbies: [] // valid
# hobbies: ['cricket', 'movies'] // valid
# hobbies: ['cricket', null, 'movies'] // valid
}
type User {
...
hobbies: [String!]!
# This means that the hobbies field cannot be null and cannot have any null values
# hobbies: null // error
# hobbies: [] // valid
# hobbies: ['cricket', 'movies'] // valid
# hobbies: ['cricket', null, 'movies'] // error
}

The __typename field

Each object type in your GraphQL schema has a field named __typename by default. We do not need to define it. This field returns the object type's name as a String (e.g., User or Post). You can get the __typename field in a query or mutation response

For example:

# QUERY
query getUserById {
getUserById(id: "1") {
__typename
name
email
}
}
# RESPONSE
{
"data": {
"getUserById": {
"__typename": "User",
"name": "John Doe",
"email": "johndoe@hygraph.com"
}
}
}

Union and Interface Type

Just like the programming concept we have an Interface type in GraphQL types too. An Interface defines a set of fields that can be implemented by several other Object types; when an object type implements an interface it must include all the fields of the interface and can also include additional fields.

For example

interface Product {
id: ID!
name: String!
}
type Book implements Product {
id: ID!
name: String!
author: String!
isbn: String!
}
type Clothing implements Product {
id: ID!
name: String!
size: String!
color: String!
}

Here we have an interface Product, which is implemented by types Book and Clothing. Type Book and Clothing need to have the fields id and name since they are defined in the interface Product.

Union types are used when you want to define a field that can output into more than one type.

For example

union SearchResult = Movie | Music

The union type SearchResult here denotes that it can either be an Object type of Movie or Music. One important thing to note about union type is that we can only use Object types while defining a union.

The example below is invalid:

union PostalCode = String | Int

Conclusion

In summary, understanding GraphQL schemas and types is crucial for building GraphQL APIs. A GraphQL schema is a collection of types that define the layout and shape of data. The GraphQL type system is composed of scalar types, object types, input types, enumeration types, union types, interface types, and type modifiers. We can use the GraphQL type system to define the GraphQL schema as per our use case and then proceed on building powerful APIs that can acquire data from various sources.