What is a GraphQL Schema
At the core of every GraphQL server lies a Schema. A GraphQL schema will serve as documentation for clients that want to integrate with the server, but there is more to it than that. GraphQL servers are extremely tightly coupled to its schema when it comes to code implementation and validations.
The Documentation Part
GraphQL has its own schema definition language (SDL) used to describe the data types and operations that the server supports. The schema always has (up to) three root types that serve as the entrypoint to the server. By default these are named “Query” for asking for data, “Mutation” for changing data, and “Subscription” for receiving real-time updates from the server.
Each data type can either contain leaf nodes which are basic data types like strings, ints or floats, or connect to other types. There are no limitations on how types can be connected to each other. It does not have to mirror any existing connections like in your database schema. If you can “reach” some other piece of data from where you are at, and it mirrors the client demands, connecting them in you schema makes sense.
To each field you can also add a description in markdown. This part removes the need to maintain a separate documentation page for your API. You can just document it in markdown! However, if you need to document things outside the GraphQL domain, but relevant to integration (like header values for example), you still need to come up with something for that.
The Implementation Part
Every field of a GraphQL type is connected to a user-defined function in a GraphQL server. Some GraphQL servers have default functions for the common use case of just mirroring whatever underlying data model you are working with. For leaf nodes, the function should just return whatever the exact data should be sent in the response. When the resolving function should connect to a different GraphQL type, it should just respond with a non-null value.
However, when you are writing your GraphQL resolving functions, you are basing that logic off of the return value from the “parent resolver” (the function that created the connection). If you respond with different shapes of data in different places you are going to be in a world of hurt when it comes to maintaining a sane implementation.
The smart thing to do is to always use the same shape of data in your resolvers that connect to new GraphQL types. If you are using TypeScript you can use GraphQL Code Generator to define TS types for each GraphQL type, and in that way get type safety in your implementation.
The Validation Part
The GraphQL schema describes every input and output with types. A great benefit to that is that the GraphQL runtime can actually make sure that the user input matches our expectations. It can’t validate everything, like maximum lengths of arrays or positive Integer values, but it helps with removing a lot of manual documentation efforts and validation code.
You have to be careful though that your implementation code makes the same guarantees as your schema does. If you schema describes something as non-nullable, you’d better never send null in the resolver function! If you do, the GraphQL server will climb up the tree to the closest nullable value, and set that to null. It will also respond with an error. If you are using Apollo Client in the frontend, the
error field will also be set to non-null, and probably trigger some error UI code branch in your frontend.