apollo-server/docs/source/essentials/schema.md
2018-05-02 17:08:30 -07:00

8.9 KiB
Raw Blame History

title sidebar_title
Understanding schema concepts Writing a schema

Estimated time: About 6 minutes.

A GraphQL schema is at the center of any GraphQL server implementation and describes the functionality available to the clients which connect to it.

The core building block within a schema is the "type". Types provide a wide-range of functionality within a schema, including the ability to:

  • Create relationships between types (e.g. between a Book and an Author).
  • Define which data-fetching (querying) and data-manipulation (mutating) operations can be executed by the client.
  • If desired, self-explain what capabilities are available to the client (via introspection).

By the end of this page, we hope to have explained the power of types and how they relate to a GraphQL server.

Schema Definition Language

To make it easy to understand the capabilities of a server, GraphQL implements a human-readable schema syntax known as its Schema Definition Language, or "SDL". The SDL is used to express the types available within a schema and how those types relate to each other.

At first glance the SDL may appear to be similar to JavaScript, but this GraphQL-specific syntax must be stored as a String. Right now, we'll focus on explaining SDL and then go into examples of using it within JavaScript later on.

In a simple example involving books and authors, the SDL might declare:

type Book {
  title: String
  author: Author
}

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

It's important to note that these declarations express the relationships and the shape of the data to return, not where the data comes from or how it might be stored - which will be covered outside the SDL.

By drawing these logical connections in the schema definition, we can allow the client (which is often a human developer, designing a front-end) to see what data is available and request it in a single optimized query.

GraphQL clients (such as Apollo Client) benefit from the precision of GraphQL operations, especially when compared to traditional REST-based approaches, since they can avoid over-fetching and stitching data, which are particularly costly on slow devices or networks.

Scalar types

Scalar types represent the leaves of an operation and always resolve to concrete data. The default scalar types which GraphQL offers are:

  • Int: Signed 32bit integer
  • Float: Signed double-precision floating-point value
  • String: UTF8 character sequence
  • Boolean: true or false
  • ID (serialized as String): A unique identifier, often used to refetch an object or as the key for a cache. While serialized as a String, ID signifies that it is not intended to be humanreadable

These primitive types cover a majority of use cases. For other use cases, we can create custom scalar types.

Object types

The object type is the most common type used in a schema and represents a group of fields. Each field inside of an object type maps to another type, allowing nested types and circular references.

type TypeName {
  fieldA: String
  fieldB: Boolean
  fieldC: Int
  fieldD: CustomType
}

type CustomType {
  circular: TypeName
}

The Query type

A GraphQL query is for fetching data and compares to the GET verb in REST-based APIs.

In order to define what queries are possible on a server, the Query type is used within the SDL. The Query type is one of many root-level types which defines functionality (it doesn't actually trigger a query) for clients and acts as an entry-point to other more specific types within the schema.

Using the books and author example we created in the SDL example of the last section, we can define multiple independent queries which are available on a server:

type Query {
  getBooks: [Book]
  getAuthors: [Author]
}

In this Query type, we define two types of queries which are available on this GraphQL server:

  • getBooks: which returns a list of Book objects.
  • getAuthors: which returns a list of Author objects.

Those familiar with REST-based APIs would normally find these located on separate end-points (e.g. /api/books and /api/authors), but GraphQL allows them to be queried at the same time and returned at once.

As mentioned in the previous section, the structure in which types are organized in the SDL is important because of the relationships it creates. When a client makes a query to the server, the server will return results in a shape that matches that of the query.

Based on the SDL defined above, a client could request a list of all books and a separate list of all authors by sending a single query with exactly what it wishes to receive in return:

query {
  getBooks {
    title
  }

  getAuthors {
    name
  }
}

Which would return data to the client as:

{
  "data": {
    "getBooks": [
      {
        "title": "..."
      },
      ...
    ],
    "getAuthors": [
      {
        "name": "..."
      },
      ...
    ]
  }
}

While having two separate lists—a list of books and a list of authors—might be useful for some purposes, a separate desire might be to display a single list of books which includes the author for each book.

Thanks to the relationship between Book and Author, which is defined in the SDL above, such a query could be expressed as:

query {
  getBooks {
    title
    author {
      name
    }
  }
}

And, without additional effort on its part, the client would receive the information in the same shape as the request:

{
  "data": {
    "getBooks": [
      {
        "title": "..."
        "author": {
          "name": "..."
        }
      },
      ...
    ]
  }
}

The Mutation type

Mutations are operations sent to the server to create, update or delete data. These are comparable to the PUT, POST, PATCH and DELETE verbs on REST-based APIs.

Much like how the Query type defines the entry-points for data-fetching operations on a GraphQL server, the root-level Mutation type specifies the entry points for data-manipulation operations.

For example, when imagining a situation where the API supported adding a new Book, the SDL might implement the following Mutation type:

type Mutation {
  addBook(title: String, author: String): Book
}

This implements a single addBook mutation which accepts title and author as arguments (both String types). We'll go further into arguments (also known as "input types") in types, but the important thing to note here is that this mutation will return the newly-created Book object.

The Book object will match the previously-created Book type (from above) and, much like the Query type, we specify the fields to include in the return object when sending the mutation:

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

In the above example, we've requested the book's title along with the name of the author. The result returned from this mutation would be:

{
  "data": {
    "addBook": {
      {
        "title": "Fox in Socks",
        "author": {
          "name": "Dr. Seuss"
        }
      }
    }
  }
}

Multiple mutations may be sent in the same request, however they will be executed in the order they are provided (in series), in order to avoid race-conditions within the operation.

Documenting your schema

Describing types

GraphQL supports providing markdown-enabled descriptions within the schema, which makes it easy for consumers of the API to discover a field and how to use it.

For example, the following type definition shows how to use both single-line string literals, as well as multi-line blocks.

"Description for the type"
type MyObjectType {
  """
  Description for field
  Supports **multi-line** description for your [API](http://example.com)!
  """
  myField: String!

  otherField(
    "Description for argument"
    arg: Int
  )
}

This makes SDL-generation even easier since many GraphQL tools (like GraphQL Playground and GraphiQL) auto-complete field names, along with the descriptions, when available.

Introspection

Introspection is an optional feature, enabled by default during development, which allows clients (which are frequently developers, building an application) to automatically discover the types implemented within a GraphQL schema.

By allowing the consumer of the API to see the full possibilities that an API can, developers can easily add new fields to existing queries.

Next steps

At this point, we hope to have explained the basic information necessary to understand a GraphQL schema.

In the next step, we'll show how to start implementing a simple GraphQL server.