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 offset
and limit
parameters to allow pagination. By default the offset
is 0
. It returns a SearchResult
which has a mandatory array of results of non-null Product
's.
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
Now that we have a parsed schema we can serve it and use GraphiQL as a schema browser. We are using Facebook’s express-graphql.
Browsing to 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
If you were to replace the mock implementation of the schema with actual calls to a backend you get automatic response validation. You can also make a validation tool which doesn’t require any JavaScript knowledge from the backend engineer.
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.
Using graphql-compose
and isValidJSValue
from graphql-js
to validate SearchResult
values could look like this:
errors
will be an array of strings:
These error messages can be understood by any developer without much prior knowledge of GraphQL or JavaScript. Using this validation code a developer friendly command line tool can be created so no JavaScript knowledge is required for a backend developer to validate their response payloads. An example of such a commandline tool is available on GitHub.
Conclusion
GraphQL’s schema language allows the definition of complex data structures. Although this example is using the JavaScript implementation, graphql-java and sangria-graphql for Scala also support the schema language. However, it seems like the tooling to generate mocking for those languages currently seems to be lacking. Implementing that functionality would be good way to contribute.
All code for this blogpost is available on GitHub.