apollo-server/docs/source/essentials/schema.md
2018-04-24 17:58:44 +03:00

8.7 KiB

title sidebar_title
Building a GraphQL schema Building a schema

Overview

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 (SDL)

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". This GraphQL-specific syntax is encoded as a String type.

The SDL is used to express the "types" available within a schema and how those types relate to each other. 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, 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.

Queries

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

Thanks to the relationships which have been defined in the SDL, the client can expect that the shape of the data returned will match the shape of the query it sends.

In order to define what queries are possible on a server, a special Query type is defined within the SDL. The Query type is one of many root-level types, but the Query type specializes in fetching data and acts as the entry-point to other types within the schema.

Using the books and author example we created in the SDL example above, we can define multiple 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.

Using this query, a client could request a list of all books and a separate list of all authors by sending a single query with exactly the types it wishes to receive in return:

query {
  getBooks {
    title
  }

  getAuthors {
    name
  }
}

The data returned from this query would look like:

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

Of course, when displaying this data on the client, it might be desirable for the Author to be within the Book that the author wrote.

Thanks to the relationship between Book and Author, which is defined in the SDL above, this query would look like:

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": "..."
        }
      },
      ...
    ]
  }
}

Mutations

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.

Introspection and documentation

Introspection is an optional feature, enabled by default during development, which allows clients to automatically discover the types implemented within a GraphQL schema.

The type declarations can be further extended with optional string literals to provide descriptions of a field's purpose to the client:

type Book {
  "Returns the title of the book, as listed in the card catalog."
  title: String
}

type Query {
  "Fetch a list of our extensive book collection!"
  getBooks: [Book]
}

This makes SDL-generation even easier since many GraphQL tools auto-complete field names, along with the descriptions, when available.

Operation names

When sending the queries and mutations in the above examples, we've used either query { ... } or mutation { ... } respectively. While this is fine, and particularly convenient when running queries by hand, it makes sense to name the operation in order to quickly identify operations during debugging or to aggregate similar operations together for application performance metrics, for example, when using Apollo Engine to monitor an API.

Operations can be named by placing an identifier after the query or mutation keyword, as we've done with HomeBookListing here:

query HomeBookListing {
  getBooks {
    title
  }
}

Variables

In the examples above, we've used static strings as values for both queries and mutations. This is a great shortcut when running "one-off" operations, but GraphQL also provides the ability to pass variables as arguments and avoid the need for clients to dynamically manipulate operations at run-time.

By defining a map of variables on the root query or mutation operation, which are sent from the client, variables can be used (and re-used) within the types and fields themselves.

For example, with a slight change to the mutation we used in the previous step, we enable the client to pass title and author variables alongside the operation itself. We can also provide defaults for those variables for when they aren't explicitly set:

mutation HomeQuickAddBook($title: String, $author: String = "Anonymous") {
  addBook(title: $title, author: $author) {
    title
  }
}

GraphQL clients, like Apollo Client, take care of sending the variables to the server separate from the operation itself:

{
  "query": "...",
  "variables": { "title": "Green Eggs and Ham", "author": "Dr. Seuss" }
}

This functionality is also supported by tools like GraphQL Playground and GraphiQL.

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.