Last time we talked about the caveats about migrating or creating a new API with GraphQL. This time we’re going more into GraphQL and discuss the Schema.
Schema Definition Language was recently added to the GraphQL Spec.
The schema is how we know what types and what is valid to query (or mutate) in the server. The Schema Definition Language (SDL), as it is known, defines types and relationships between types. The basic ‘unit’ of work is the object type (represents an object) which has fields.
type User {
id: ID!
name: String
posts: [Post]
}
In the previous example, the User
is the object type and has three fields which are id
, name
and posts
. As you can see, the id
field is of type ID
which is one of the default scalar types available in GraphQL, the other being: Int
, Float
, String
and Boolean
. The exclamation point !
means that the value is not null, which says that the server promises always to return a value for the given field. Fields are nullable by default in GraphQL. Is worth noting that every field is open to accept arguments.
type Post {
id: ID!
user: User
title: String
content: String
date(format: DateFormat = SHORTFORMAT): String
}
Fragments
Fragments are a neat little feature that let you define a reusable set of fields over an object type. For example, let’s say you want to display two queries over the same type.
{
leftUser: userById(id: 1) {
...fieldsToCompare
}
rightUser: userById(id: 2) {
...fieldsToCompare
}
}
fragment fieldsToCompare on User {
name
posts {
title
}
}
That way, you don’t have to rewrite the same fields on both queries; instead, you reuse the fragment.
Query and Mutations
GraphQL defines two special types which are Query
and Mutation
which should be available as your root types, which hold either all available queries or mutations respectively.
type Query {
allUsers: [User]
}
The previous example allows us to define a query over an array of users with the fields we specifically need. Let’s assume that the Post
type has a field called title
.
query {
allUsers {
name
posts {
title
}
}
}
Input Types
When dealing with complex arguments, you can define input types which are like regular object types but only used for arguments. As a caveat, if you want to build an input type that has another nested object type, it has to be defined as another input type, the usual type AnotherType
will throw an error.
input Coordinates {
latitude: String
longitude: String
}
type Store {
id: ID!
name: String
}
type Query {
storeByCoordinates(coordinates: Coordinates!)
}
Directives
Directives are a featured that are attached to fields, which affects the execution of the query in any way the server desires. There are only two directives defined in the core GraphQL specification, which are @include(if: Boolean)
and @skip(if: Boolean)
. This directives either include or skip a field if the boolean argument is either true or false. As with scalar values, you can define your directives for your server.
Conclusion
Having the ability to quickly review and understand queries and the types defined by our domain is what allows us to match what front-ends need from the server and have a product first approach to designing APIs. Also, having this is what allows client-server communication to validate queries effectively, letting clients know what field is not valid, or matching expectations of what field is either null or not. A few things are missing from this post like Mutations, interfaces and union types. Let me know what your best practices for defining your Schema are!