Design by contract using GraphQL
When interfacing between systems it is good practice to think about the interface design prior to developing the systems. GraphQL can be a useful tool to write down these design decisions using its schema definition language. Even when you are not using GraphQL itself in production. GraphQL’s schema can be used to generate a mock server for clients and can verify whether the responses of the server are valid. This way a clear and precise agreement on the API can be made upfront to avoid costly surprises at the end of the development phase.
Although everyone is a fullstack developer nowadays, it is still often the case that the frontend and the backend are not developed by the same person or even the same team. Even when a developer could implement a functionality end to end it could be beneficial to have two developers work in parallel on the backed and the frontend. This means that when you are working on a new feature which depends on new data from the backend there needs to be some communication between the frontend and backend developer. This will ensure upfront that the two systems will integrate properly.
Far too often I’ve seen the last-minute refactoring because the data provided by the backend does not match the data that was assumed to be provided by the frontend developer. Having a contract ensures everyone is on the same page from the beginning. It also forces everyone to think about all the fields upfront. Sometimes it a single field that is unavailable can cause the impact of a change to be much larger initially anticipated.
Writing down the interface definitions prior to development is called designing by contract. GraphQL’s schema is the contract that can verify JSON responses of the server. It is fully statically typed and can even make the distinction between optional and required fields.
Making the schema
GraphQL schemas can be created using plain JSON as well as using its schema language. Typically this schema is used to validate GraphQL queries but it can also validate any JSON structure.
As an example we will create a product search API like you might have at an online retailer. A schema should have query as a root of its schema. In this
Query type we define a
search endpoint. It uses the
limit parameters to allow pagination. By default the
0. It returns a
SearchResult which has a mandatory array of results of non-null
GraphQL’s schema language is more powerful than shown in this example, but that is outside of the scope of this blogpost.
Using the schema to generate mocks
To use the schema it needs to be compiled. We’re gonna use
graphql-tools from the Apollo team to do this.
graphql-tools provides a clean API to parse the schema as well as implement it. It also has functionality to automatically generate a mock implementation. It is only an API abstraction which uses Facebook's graphql-js.
In this code we read the schema definition which is located at
schema.graphql from the filesystem. Then we parse it by calling
makeExecutableSchema and add a mock implementation. A small gotcha is that
makeExecutableSchema requires an array of schema definitions.
That is all you need to do to create a working GraphQL schema. However, the default mock implementation generated by
graphql-tools always returns
Hello World as a text value and also generates negative numbers for the total number of products. To fix this we use the
casual library to generate mock data. It can easily generate mock data with varying requirements.
Note you only need to specify mock data for the fields where you want to override the default mock behavior of
graphql-tools. In this example we are fine with the mock generated for the
id field and therefore don't specify mock data for that field.
Building a GraphQL server
http://localhost:3000/graphql allows you to check whether everything works as expecting. You can use
GraphiQL's schema browser as well as its autocomplete to construct queries. Note this is only needed when you are going to use GraphQL in production. It is also possible to just use GraphQL during development.
Querying your server
Querying a GraphQL server requires a query being sent to a server. An example query which fetches all of the product data can look like this.
To send this to the server you could use cURL or use the
fetch API like so:
This prints a mock result like this:
Validating JSON values
graphql-js offers a method
isValidJSValue that works on input types which can be found in the parsed schema we created before. However, we want to validate the
SearchResult type which is a response type. Fortunately graphql-compose can help to convert a response type to an input type.
It makes sense that
graphql-js can only validate input types by default because those represent the request payload which need to be fully valid. Response payloads only need to have valid fields when they are requested. Fields that are not queried are not validated. Additionally input types can't have more fields than defined in the schema whereas when response types have fields which are not defined in the schema, they are ignored and not sent to clients.
graphql-js to validate
SearchResult values could look like this:
errors will be an array of strings:
All code for this blogpost is available on GitHub.