GraphQL 101: The Type System

A schema defines a GraphQL API's type system. It describes the complete set of possible data (objects, fields, relationships, everything) that a client can access. Calls from the client are validated and executed against the schema. More on validation and execution in the next and final article in this series.

Schema

The schema defines the capabilities of a GraphQL server. While the schema can be written in a programming language, it is often written in SDL (Schema Definition Language). The choice of language is driven by the decision of which method to use in building the schema. It can be built using either schema-first or code-first schema creation. Here is an example from Github's schema, written in SDL. This is a complex example but you will see the type breakdowns in the simpler examples covered below. I like this example because it is a real-world example, well documented, and includes many of the syntax elements that we will discuss.

"""
Represents an object which can take actions on GitHub.
Typically a User or Bot.
"""
interface Actor {
  """
  A URL pointing to the actor's public avatar.
  """
  avatarUrl(
    """
    The size of the resulting square image.
    """
    size: Int
  ): URI!

  """
  The username of the actor.
  """
  login: String!

  """
  The HTTP path for this actor.
  """
  resourcePath: URI!

  """
  The HTTP URL for this actor.
  """
  url: URI!
}

"""
Location information for an actor
"""
type ActorLocation {
  """
  City
  """
  city: String

  """
  Country name
  """
  country: String

  """
  Country code
  """
  countryCode: String

  """
  Region name
  """
  region: String

  """
  Region or state code
  """
  regionCode: String
}

Types

There are six named types and two wrapping types. The named types are grouped here and include, scalars, enums, objects, input objects, interfaces, and unions.

NOTE: If you think of a GraphQL query as a tree, starting at the root field and branching out, the leaves are either scalars or enums. They’re the fields without selection sets of their own.

Named Types

  • Scalars: Scalars are primitive values. There are five included scalar types:
  • Int: Signed 32-bit non-fractional number. Maximum value around 2 billion (2,147,483,647).
  • Float: Signed double-precision (64-bit) fractional value.
  • String: Sequence of UTF-8 (8-bit Unicode) characters.
  • Boolean: true or false.
  • ID: Unique identifier, serialized as a string
  • We can also define our own scalars, like Url and DateTime. In the description of our custom scalars, we write how they’re serialized so the frontend developer knows what value to provide for arguments. For instance, DateTime could be serialized as an ISO string.
scalar DateTime

type Mutation {
  dayOfTheWeek(when: DateTime): String
}
  • Enums: When a scalar field has a small set of possible values, it’s best to use an enum instead. The enum type declaration lists all the options.
type Issue {
    enum State {
       OPEN
       CLOSED
    }
}

Objects: An object is a list of fields, each of which has a name and a type. The below schema defines an object type for ActorLocation and the fields within this object.

type ActorLocation {
  city: String
  country: String
  countryCode: String
  region: String
  regionCode: String
}
  • Input objects: Input objects are objects that are only used as arguments. An input object is often the sole argument for mutations. An input object can be a list of input fields — scalars, enums, and other input objects.
input AcceptTopicSuggestionInput {
  clientMutationId: String
  name: String!
  repositoryId: ID!
}
  • Interfaces: Interfaces serve as parent objects from which other objects can inherit.  For example from Github's schema, Lockable is an interface because both Issue and PullRequest objects can be locked. An interface has its own list of named fields that are shared by implementing objects. Below is a contrived example of a schema that defines interface X and object Y.
interface X {
  some_field: String!
  other_field: String!
}

type Y implements X {
  some_field: String!
  other_field: String!
  new_field: String!
}
  • Unions: A union type is defined as a list of object types. When a field is typed as a union, its value can be any of the objects listed in the union definition.
"""
Types that can be assigned to issues.
"""
union Assignee = Bot | Mannequin | Organization | User
"""
Types that can initiate an audit log event.
"""
union AuditEntryActor = Bot | Organization | User

Wrapped Types

  • Lists: A List wraps another type and signifies an ordered list in which each item is of the wrapped type.
  • Non-null: Non-null wraps any other type and signifies that type can’t be null. A field is declared to be non-nullable with a !.
  extendedDescriptionHTML: HTML!

Descriptions

We can add a description before any definition in our schema using " or """. Descriptions are included in introspection and displayed by tools like GraphiQL, an IDE for creating GraphQL queries. You can see in the Schema snippet above that all of the text between """ are descriptions of the objects and fields defined between them.

"""
Represents an object which can take actions on GitHub.
Typically a User or Bot.
"""
interface Actor {
  """
  A URL pointing to the actor's public avatar.
  """
  avatarUrl(
    """
    The size of the resulting square image.
    """
    size: Int

Directives

We talked about the query side of directives in The Query Language Part 1. Directives are declared in the schema. A directive definition includes its name, any arguments, what types of locations it can be used, and whether it’s repeatable (can be used multiple times in the same location). The locations can either be in executable documents (requests from the client) or schema documents.

Extending

All named types can be extended in some way. We might extend types when we’re defining our schema across multiple files, or if we’re modifying a schema defined by someone else.

  """
  URL to the listing's documentation.
  """
  documentationUrl: URI

  """
  The listing's detailed description.
  """
  extendedDescription: String

  """
  The listing's detailed description rendered to HTML.
  """
  extendedDescriptionHTML: HTML!

Introspection

GraphQL is introspective. This means you can query a GraphQL schema for details about itself. We can query __schema to list all types defined in the schema and get details about each. It is also possible to run a query on __type to return the details on an individual type. In addition, we can run an introspection query of the schema via a GET request like in the curl command below. All of these methods of introspection will return JSON.

query {
  __schema {
    types {
      name
      kind
      description
      fields {
        name
      }
    }
  }
}
query {
  __type(name: "Repository") {
    name
    kind
    description
    fields {
      name
    }
  }
}
curl -H "Authorization: bearer token" https://api.github.com/graphql

Subscribe to DocDocGo

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe