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
- Object including root operation types:
Query,Mutation, andSubscription - Input
- Enum
- Union
- Interface
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:
IntFloatStringBooleanID(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
}