Schemas and Types
Learn about the different elements of the GraphQL type system
The GraphQL type system describes what data can be queried from the API. The collection of those capabilities is referred to as the service’s schema and clients can use that schema to send queries to the API that return predictable results.
On this page, we’ll explore GraphQL’s six kinds of named type definitions as well as other features of the type system to learn how they may be used to describe your data and the relationships between them. Since GraphQL can be used with any backend framework or programming language, we’ll avoid implementation-specific details and talk only about the concepts.
Type system
If you’ve seen a GraphQL query before, you know that the GraphQL query language is basically about selecting fields on objects. So, for example, in the following query:
- We start with a special “root” object
- We select the
herofield on that - For the object returned by
hero, we select thenameandappearsInfields
Because the shape of a GraphQL query closely matches the result, we can predict what the query will return without knowing that much about the server. But it’s useful to have an exact description of the data we can request. For example, what fields can we select? What kinds of objects might they return? What fields are available on those sub-objects?
That’s where the schema comes in. Every GraphQL service defines a set of types that completely describe the set of possible data we can query on that service. Then, when requests come in, they are validated and executed against that schema.
Type language
GraphQL services can be written in any language and there are many different approaches you can take when defining the types in a schema:
- Some libraries have you construct the schema types, fields, and resolver functions together using the same programming language that was used to write the GraphQL implementation.
- Some libraries allow you to define types and fields more ergonomically using what’s commonly called the schema definition language (or SDL) and then write the resolver functions for the corresponding fields separately.
- Some libraries allow you to write and annotate the resolver functions, and then infer the schema from that.
- Some libraries may even infer both the types and resolver functions for you, based on some underlying data source(s).
Since we can’t rely on a specific programming language to discuss GraphQL schemas in this guide, we’ll use SDL because it’s similar to the query language that we’ve seen so far and allows us to talk about GraphQL schemas in a language-agnostic way.
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 SDL, we represent it like this:
type Character {
name: String!
appearsIn: [Episode!]!
}The language is readable, but let’s go over it so that we can have a shared vocabulary:
Characteris a GraphQL Object type, meaning it’s a type with some fields. Most of the types in your schema will be Object types.nameandappearsInare fields on theCharactertype. That means thatnameandappearsInare the only fields that can appear in any part of a GraphQL query that operates on theCharactertype.Stringis one of the built-in Scalar types. These are types that resolve to a single scalar value and can’t have sub-selections in the query. We’ll go over Scalar types more later.String!means that the field is a Non-Null type, meaning the GraphQL service promises to give you a value whenever you query this field. In SDL, we represent those with an exclamation mark.[Episode!]!represents a List type ofEpisodeobjects. When a List is Non-Null, you can always expect an array (with zero or more items) when you query theappearsInfield. In this case, sinceEpisode!is also Non-Null within the list, you can always expect every item in the array to be anEpisodeobject.
Now you know what a GraphQL Object type looks like and how to read the basics of SDL.
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 such as 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 called 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, then it will be set to METER by default.
The Query, Mutation, and Subscription types
Every GraphQL schema must support query operations. The entry point for this root operation type is a regular Object type called Query by default. So if you see a query that looks like this:
That means that the GraphQL service needs to have a Query type with a droid field:
type Query {
droid(id: ID!): Droid
}Schemas may also support mutation and subscription operations by adding additional Mutation and Subscription types and then defining fields on the corresponding root operation types.
It’s important to remember that other than the special status of being entry points into the schema, the Query, Mutation, and Subscription types are the same as any other GraphQL Object type, and their fields work exactly the same way.
You can name your root operation types differently too; if you choose to do so then you will need to inform GraphQL of the new names using the schema keyword:
schema {
query: MyQueryType
mutation: MyMutationType
subscription: MySubscriptionType
}Scalar types
A GraphQL Object type has a name and fields, but at some point, those fields must resolve to some concrete data. That’s where the Scalar types come in: they represent the leaf values of the query.
In the following query, the name and appearsIn fields will resolve to Scalar types:
We know this because those fields don’t have any sub-fields—they are the leaves of the query.
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:trueorfalse.ID: A unique identifier, often used to refetch an object or as the key for a cache. TheIDtype is serialized in the same way as aString; however, defining it as anIDsignifies that it is not intended to be human‐readable.
In most GraphQL service implementations, there is also a way to specify custom Scalar types. For example, we could define a Date type:
scalar DateThen 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.
Enum types
Enum types, also known as enumeration types, are a special kind of scalar that is restricted to a particular set of allowed values. This allows you to:
- Validate that any arguments of this type are one of the allowed values
- Communicate through the type system that a field will always be one of a finite set of values
Here’s what an Enum type definition looks like in SDL:
enum Episode {
NEWHOPE
EMPIRE
JEDI
}This means that wherever we use the type Episode in our schema, we expect it to be exactly one of NEWHOPE, EMPIRE, or JEDI.
Type modifiers
Types are assumed to be nullable and singular by default in GraphQL. However, when you use these named types in a schema (or in query variable declarations) you can apply additional type modifiers that will affect the meaning of those values.
As we saw with the Object type example above, GraphQL supports two type modifiers—the List and Non-Null types—and they can be used individually or in combination with each other.
Non-Null
Let’s look at an example:
type Character {
name: String!
}Here, we’re using a String type and marking it as a Non-Null type 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 the resolver produces a null value, then that will trigger a GraphQL execution error, letting the client know that something has gone wrong.
As we saw in an example above, 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: