GraphQL Schema Basics


Your GraphQL server uses a schema to describe the shape of your available data. This schema defines a hierarchy of types with fields that are populated from your back-end data stores. The schema also specifies exactly which queries and mutations are available for clients to execute.

This article describes the fundamental building blocks of a schema and how to create one for your GraphQL server.

The schema definition language

The GraphQL specification defines a human-readable schema definition language (or SDL) that you use to define your schema and store it as a string.

Here's a short example schema that defines two object types: Book and Author:

GraphQL
schema.graphql
1type Book {
2  title: String
3  author: Author
4}
5
6type Author {
7  name: String
8  books: [Book]
9}

A schema defines a collection of types and the relationships between those types. In the example schema above, a Book can have an associated author, and an Author can have a list of books.

Because these relationships are defined in a unified schema, client developers can see exactly what data is available and then request a specific subset of that data with a single optimized query.

Note that the schema is not responsible for defining where data comes from or how it's stored. It is entirely implementation-agnostic.

Field definitions

Most of the schema types you define have one or more fields:

GraphQL
1# This Book type has two fields: title and author
2type Book {
3  title: String # returns a String
4  author: Author # returns an Author
5}

Each field returns data of the type specified. A field's return type can be a scalar , object , enum , union , or interface (all described below).

List fields

A field can return a list containing items of a particular type. You indicate list fields with square brackets [], like so:

GraphQL
1type Author {
2  name: String
3  books: [Book] # A list of Books
4}

List fields can be nested by using multiple pairs of square brackets [[]].

Field nullability

By default, it's valid for any field in your schema to return null instead of its specified type. You can require that a particular field doesn't return null with an exclamation mark !, like so:

GraphQL
1type Author {
2  name: String! # Can't return null
3  books: [Book]
4}

These fields are non-nullable. If your server attempts to return null for a non-nullable field, an error is thrown.

Nullability and lists

With a list field, an exclamation mark ! can appear in any combination of two locations:

GraphQL
1type Author {
2  books: [Book!]! # This list can't be null AND its list *items* can't be null
3}
  • If ! appears inside the square brackets, the returned list can't include items that are null.

  • If ! appears outside the square brackets, the list itself can't be null.

  • In any case, it's valid for a list field to return an empty list.

Based on the above principles, the below return types can potentially return these sample values:

Return TypeExample Allowed Return Values
[Book][], null, [null], and [{title: "City of Glass"}]
[Book!][], null, and [{title: "City of Glass"}]
[Book]![], [null], and [{title: "City of Glass"}]
[Book!]![] and [{title: "City of Glass"}]

Supported types

Every type definition in a GraphQL schema belongs to one of the following categories:

Each of these is described below.

Scalar types

Scalar types are similar to primitive types in your favorite programming language. They always resolve to concrete data.

GraphQL's default scalar types are:

  • 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 (serialized as a String): A unique identifier that's often used to refetch an object or as the key for a cache. Although it's serialized as a String, an ID is not intended to be human‐readable.

These primitive types cover the majority of use cases. For more specific use cases, you can create custom scalar types .

Object types

Most of the types you define in a GraphQL schema are object types. An object type contains a collection of fields , each of which has its own type.

Two object types can include each other as fields, as is the case in our example schema from earlier:

GraphQL
1type Book {
2  title: String
3  author: Author
4}
5
6type Author {
7  name: String
8  books: [Book]
9}

The __typename field

Every object type in your schema automatically has a field named __typename (you don't need to define it). The __typename field returns the object type's name as a String (e.g., Book or Author).

GraphQL clients use an object's __typename for many purposes, such as to determine which type was returned by a field that can return multiple types (i.e., a union or interface ). Apollo Client relies on __typename when caching results, so it automatically includes __typename in every object of every query.

Because __typename is always present, this is a valid query for any GraphQL server:

GraphQL
1query UniversalQuery {
2  __typename
3}

The Query type

The Query type is a special object type that defines all of the top-level entry points for queries that clients execute against your server.

Each field of the Query type defines the name and return type of a different entry point. The Query type for our example schema might resemble the following:

GraphQL
1type Query {
2  books: [Book]
3  authors: [Author]
4}

This Query type defines two fields: books and authors. Each field returns a list of the corresponding type.

With a REST-based API, books and authors would probably be returned by different endpoints (e.g., /api/books and /api/authors). The flexibility of GraphQL enables clients to query both resources with a single request.

Structuring a query

When your clients build queries to execute against your graph, those queries match the shape of the object types you define in your schema.

Based on our example schema so far, a client could execute the following query, which requests both a list of all book titles and a list of all author names:

GraphQL
1query GetBooksAndAuthors {
2  books {
3    title
4  }
5
6  authors {
7    name
8  }
9}

Our server would then respond to the query with results that match the query's structure, like so:

JSON
1{
2  "data": {
3    "books": [
4      {
5        "title": "City of Glass"
6      },
7      ...
8    ],
9    "authors": [
10      {
11        "name": "Paul Auster"
12      },
13      ...
14    ]
15  }
16}

Although it might be useful in some cases to fetch these two separate lists, a client would probably prefer to fetch a single list of books, where each book's author is included in the result.

Because our schema's Book type has an author field of type Author, a client could instead structure their query like so:

GraphQL
1query GetBooks {
2  books {
3    title
4    author {
5      name
6    }
7  }
8}

And once again, our server would respond with results that match the query's structure:

JSON
1{
2  "data": {
3    "books": [
4      {
5        "title": "City of Glass",
6        "author": {
7          "name": "Paul Auster"
8        }
9      },
10      ...
11    ]
12  }
13}

The Mutation type

The Mutation type is similar in structure and purpose to the Query type . Whereas the Query type defines entry points for read operations, the Mutation type defines entry points for write operations.

Each field of the Mutation type defines the signature and return type of a different entry point. The Mutation type for our example schema might resemble the following:

GraphQL
1type Mutation {
2  addBook(title: String, author: String): Book
3}

This Mutation type defines a single available mutation, addBook. The mutation accepts two arguments (title and author) and returns a newly created Book object. As you'd expect, this Book object conforms to the structure that we defined in our schema.

Structuring a mutation

Like queries, mutations match the structure of your schema's type definitions. The following mutation creates a new Book and requests certain fields of the created object as a return value:

GraphQL
1mutation CreateBook {
2  addBook(title: "Fox in Socks", author: "Dr. Seuss") {
3    title
4    author {
5      name
6    }
7  }
8}

As with queries, our server would respond to this mutation with a result that matches the mutation's structure, like so:

JSON
1{
2  "data": {
3    "addBook": {
4      "title": "Fox in Socks",
5      "author": {
6        "name": "Dr. Seuss"
7      }
8    }
9  }
10}

A single mutation operation can include multiple top-level fields of the Mutation type. This usually means that the operation will execute multiple back-end writes (at least one for each field). To prevent race conditions, top-level Mutation fields are resolved serially in the order they're listed (all other fields can be resolved in parallel).

Learn more about designing mutations

The Subscription type

See Subscriptions .

Input types

Input types are special object types that allow you to provide hierarchical data as arguments to fields (as opposed to providing only flat scalar arguments).

An input type's definition is similar to an object type's, but it uses the input keyword:

GraphQL
1input BlogPostContent {
2  title: String
3  body: String
4}

Each field of an input type can be only a scalar , an enum , or another input type:

GraphQL
1input BlogPostContent {
2  title: String
3  body: String
4  media: [MediaDetails!]
5}
6
7input MediaDetails {
8  format: MediaFormat!
9  url: String!
10}
11
12enum MediaFormat {
13  IMAGE
14  VIDEO
15}

After you define an input type, any number of different object fields can accept that type as an argument:

GraphQL
1type Mutation {
2  createBlogPost(content: BlogPostContent!): Post
3  updateBlogPost(id: ID!, content: BlogPostContent!): Post
4}

Input types can sometimes be useful when multiple operations require the exact same set of information, but you should reuse them sparingly. Operations might eventually diverge in their sets of required arguments.

Take care if using the same input type for fields of both Query and Mutation. In many cases, arguments that are required for a mutation are optional for a corresponding query. You might want to create separate input types for each operation type.

Enum types

An enum is similar to a scalar type, but its legal values are defined in the schema. Here's an example definition:

GraphQL
1enum AllowedColor {
2  RED
3  GREEN
4  BLUE
5}

Enums are most useful in situations where the user must pick from a prescribed list of options. As an additional benefit, enum values autocomplete in tools like the Apollo Studio Explorer.

An enum can appear anywhere a scalar is valid (including as a field argument), because they serialize as strings:

GraphQL
1type Query {
2  favoriteColor: AllowedColor # enum return value
3  avatar(borderColor: AllowedColor): String # enum argument
4}

A query might then look like this:

GraphQL