Robert Matyszewski
1/18/2019
Let's explore the next stage of our GraphQL tutorial - type system and how it defines what data can be queried. Since GraphQL can be used with any backend programming language, we'll focus mostly on general the concepts.
GraphQL can be used with any language. Since we can't rely on any specific language syntax, to talk about GraphQL schemas, we'll define our simple language. We'll use the "GraphQL schema language" - it's similar to the query and allows us to talk about GraphQL schemas in an agnostic way
GraphQL query language is basically about selecting fields on objects. Let's check that on our example:
{
Actor {
name
appearsIn
}
}
{
"data": {
"hero": {
"name": "Arnold",
"appearsIn": [
"TERMINATOR",
"CONAN",
"PREDATOR"
]
}
}
}
GraphQL query closely matches the result; therefore you can predict what the query will respond without knowing much about the server. But it's helpful to have an accurate description of the data we can ask for - what fields can we choose? What fields are possible on those sub-objects? What kinds of things might they respond?
Every GraphQL service describes a set of types which ultimately represent the set of potential data you can query on that service. Then, when queries come in, they are validated and executed against that schema.
The essential elements of a GraphQL schema are object types, which describe 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 Actor {
name: String!
appearsIn: [Movie!]!
}
The language is pretty simple, but let's go over it so that we can have a shared dictionary:
Most types in your schema will be standard object types, but two types are special within a schema:
schema {
query: Query
mutation: Mutation
}
Every GraphQL service has a query type and don't need to have a mutation type. These types are the same as a regular object type, but they are unique because they define the entry point of every GraphQL query.
query {
hero {
name
}
droid(id: "2000") {
name
}
}
{
"data": {
"hero": {
"name": "R2-D2"
},
"droid": {
"name": "C-3PO"
}
}
}
That means that the GraphQL service needs to have a Query type with hero and droid fields:
type Query {
hero(episode: Episode): Character
droid(id: ID!): Droid
}
Mutations work similarly - you define fields on the Mutation type, and those are possible as the root mutation fields you can call in your query.
It's essential to master that other than the special status of being the "entry point" into the schema, the Query and Mutation types are the same as any other GraphQL object type, and their fields act precisely the same way.
A GraphQL object type has a name and fields, but at some level those fields have to resolve to some particular data. That's where the scalar types come in: they represent the leaves of the query.
In the next query, the name and appearsIn fields will choose to scalar types. We know this because those fields don't have any sub-fields - they are the leaves of the query.
{
Actor {
name
appearsIn
}
}
{
"data": {
"hero": {
"name": "Arnold",
"appearsIn": [
"TERMINATOR",
"CONAN",
"PREDATOR"
]
}
}
}
GraphQL comes with a set of default scalar types out of the box:
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 validated serialized or deserialized. 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.
Also called Enums, enumeration types are a special kind of scalar that's limited to an appropriate 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
enum Movie {
TERMINATOR
CONAN
PREDATOR
}
It means that anywhere we use the type Movie in our schema, we expect it to be precisely one of TERMINATOR, CONAN, or PREDATOR.
Note that GraphQL service implementations in different languages will have their own programming-specific way to deal with enums. In languages that support enums as a first-class citizen, the application 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.