This article's content
Schemas and Types in GraphQL

A GraphQL schema is nothing else than the definition of your backend API. You use a schema definition language (SDL) to define your API endpoints as types with fields. There is only ever one schema per GraphQL service in a file usually called schema.graphql

// schema.graphql

type Book {
  title: String
  author: Author
}

type Author {
  name: String! # Can't return null because of "!"
  books: [Book] # A list of Books because of []
}
  • The schema is not responsible for defining where data comes from or how it’s stored
  • A schema also serves the purpose of self-documenting your API
  • Incoming queries are validated and executed against the schema
  • A schema shows the relations between your data endpoints

There are basically two common approaches on how to create a schema: You can either define it yourself by writing SDL manually (SDL-first approach) or you have it generated by classes that you annotated with decorators (code-first approach).

Supported types are

Scalar types

Scalar fields are fields that contain concrete data, they represent the leaves of the query. GraphQL comes with a set of default scalar types:

  • Int
  • Float
  • String
  • Boolean
  • ID (often used to refetch an object or as the key for a cache, can be serialized but is not intended to be human-readable)

We can also define a custom scalar type:

scalar Date

Then it’s up to our implementation to define how that type should be serialized, deserialized, and validated.

Object type

This is how to create a custom Object Type Book and Author:

type Book {
  title: String
  author: Author
}

type Author {
  name: String
  books: [Book]
}

Every object type in your schema automatically has a field named __typename that you can query for and which returns the Object type’s name as a string, Book and Author in the example above.

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.

type Query {
  books: [Book]
  authors: [Author]
}

In a REST API this would typically be implemented as two endpoints like /api/books and /api/authors, but GraphQL enables clients to query both resources with a single request. How this client request is structured is up to you:

query GetBooksAndAuthors {
  books {
    title
  }

  authors {
    name
  }
}

or

query GetBooks {
  books {
    title
    author {
      name
    }
  }
}

Mutation type

Whereas the Query type defines entry points for read operations, the Mutation type defines entry points for write operations.

The following mutation creates a new Book and requests certain fields of the created object as a return value

mutation CreateBook {
  addBook(title: "Fox in Socks", author: "Dr. Seuss") {
    title
    author {
      name
    }
  }
}

To prevent race conditions, top-level Mutation fields are resolved serially in the order they’re listed

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).

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

# Define input types

input BlogPostContent {
  title: String
  body: String
  media: [MediaDetails!]
}

input MediaDetails {
  format: MediaFormat!
  url: String!
}

enum MediaFormat {
  IMAGE
  VIDEO
}
# Example of used input types

type Mutation {
  createBlogPost(content: BlogPostContent!): Post
  updateBlogPost(id: ID!, content: BlogPostContent!): Post
}

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.

Enumerations

enum AllowedColor {
  RED
  GREEN
  BLUE
}
type Query {
  favoriteColor: AllowedColor # enum return value
  avatar(borderColor: AllowedColor): String # enum argument
}

Arguments

Here we specify an argument unit with default value METER.

type Starship {
  id: ID!
  name: String!
  length(unit: LengthUnit = METER): Float
}

About Author

Mathias Bothe To my job profile

I am Mathias, born 40 years ago in Heidelberg, Germany. Today I am living in Munich and Stockholm. I am a passionate IT freelancer with more than 16 years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I am founder of bosy.com, creator of the security service platform BosyProtect© and initiator of several other software projects.