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:
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 }