From backend to frontend typed application with GraphQL

Photo by YIFEI CHEN on Unsplash

TLDR; GraphQL type system and remote procedure execution give us an easy way to control types of incoming data. In this post, we will see how to setup a simple GraphQL server (with graphql-js) and request some data with a React application. To take advantage of the GraphQL typing frontend side, we will use graphql-code-generator to mirror the types from the backend schema to the frontend. Doing so, we will have a more confident experience with the data manipulated by the frontend application.

Github repository is available here: https://github.com/othke/graphql-typed-app

One of the greatest things brings by GraphQL are the type system and the remote procedure execution. This guarantee, that for a given request, we will always have the expected data type (or a list of errors managed by GraphQL).

For those who does not know what is GraphQL (it is not a database 😊) take a look at this blog post from one of the creator. And if you are curious to know where, why and how it appears watch this video. In a short GraphQL give you:

  • A query manipulation language to execute remote procedure
  • A strong type system
  • A simplified communication with your backend (no over fetching data)

REST APIs are great and in some situation they provide a good technical solution. The goal of GraphQL is not to replaced REST APIs, but more filling a gap. One of the recurring problems with a REST API is that it does not give us all the tools to ensure that the data you have received are what you were expected. Even with OpenAPI and a nice documentation, REST APIs does not have in their DNA the validation of types.

Let’s take a quick example with an /authors endpoint that should return data on the author and a list of its books. Here is the result of the REST API.

You may have noticed that the second author object has null value for books property. Even if the documentation (OpenAPI, etc.) mentioned that books should be a list, nothing will throw an error if this specification is not respected.
How can we avoid that? You can use tools like Joi to validate your data before sending them to the frontend application but it is not a default behavior on a REST API.

When the data arrive on the frontend application, this is the kind of problem you may encounter if you are too confident with the backend application.

Always on the frontend application, how could you know the data type coming from the backend application ? If you are using TypeScript, there is no way to determine the data type coming from an HTTP request. You will have to define the types manually on the frontend application based on the REST API specification. Maybe you can have a shared library to do that but you have to keep it synchronizes both side !

Avoid to reinventing the wheel and use tools that generate types from an OpenAPI specification. Theses tools are great because they will help you to have better experience (autocompletion) when working on the data coming from the backend application. But once again, there is no way for TypeScript to detect wrong incoming data type.

If you want to be sure that the incoming data are matching the correct type, you will have to use a validation tool, like the already mentioned Joi or AJV. With those tools you can build a validation function to be executed as soon as you get incoming data from the backend application.

Here we wanted to demonstrate that we could use REST APIs and be confident with data coming from the backend application, but that implied certain precautions.

When you execute a GraphQL query you can be confident with the result because the GraphQL execution will obligatorily validate the data shape and types before send it to the frontend application.

We are not going to deep dive 🤿 into the graphql-js implementation source code but a quick look at the GraphQL buildResponse function show us that the errors are handle by default and returned. So any invalid data type will not be send to the frontend application, instead you will receive an error property containing invalid fields which is better than an unexpected value.

Ok, so now we can trust the data coming from the backend. But how can we know on the frontend application what are the incoming data types. Same as for a REST API, a TypeScript application cannot guess the types of data coming from an HTTP request. We must use a similar tools than for REST APIs which can generate types from the backend. Luckily for us GraphQL come with an introspection mechanism which help to build the types from the schema. The tool that we are going to use is graphql-code-generator.

If you are curious of all those internal tools that GraphQL provide take time to read this article.

To illustrate that, we will build a GraphQL Server that return the NHL teams. For simplicity, we won’t use a database, data will be in a static file containing a list of teams. With this server we could request the NHL divisions and the NHL teams.

Here is the index.js from the backend application. There is comment to explain the different steps, but the more important thing is GraphQL saves us a lot of work by doing the type control himself 👍

You can play with the backend here

backend application

Now the backend is ready and typed, we want to have the same thing on the frontend application. We will build a React application with create-react-app and the TypeScript template.

With our boilerplate setup we want to request our GraphQL endpoint and know what types we are going to manipulate. To do that we are going to use graphql-code-generator. You will have to install the main package and some plugins depending on the kind of application you are running.

# Install the cli
npm install -D @graphql-codegen/cli
# Install the plugin for build typescript types
npm install -D @graphql-codegen/typescript
# Install the plugin to make types available with the react application
npm install -D @graphql-codegen/typescript-react-apollo

You can then use the cli to create the codegen.yml at the root of your project or make it manually.

# codegen.yml at the root of the project
schema: http://localhost:4000/graphql
generates:
src/generated/graphql.tsx:
plugins:
- typescript
- typescript-react-apollo

Then you can add the following script to your package.json and run it to generate your types based on the introspection of your GraphQL schema.

"generate": "graphql-codegen --config codegen.yml"

The output file contains all the TypeScript types mirroring the GraphQL schema. You will probably notice a Maybe type which is here for optional GraphQL types. If you want to avoid this, you must update your GraphQL schema definition with GraphQLNonNull for all the fields that should not be optionals.

In the React application we will use Apollo client to make GraphQL Request but you could use what you want. In the following example we are going to request the teams field who should return a list of team (maybe).

We get the expected type from this line by picking into the Query type the property teams.

type GetTeams = Pick<Query, "teams">;

Then we specify this type to the generic useQuery<T> from Apollo client. Doing that TypeScript will know the expected type coming from this GraphQL query. This introduce a strong type checking into your React component and avoid you some potential errors caused by an undefined or unexpected incoming value.

const { data } = useQuery<GetTeams>(GET_TEAMS, {
variables: { division, filter },
});

In this example, we don’t handle error coming from GraphQL. In a real application you should manage this case displaying a message, logging or throwing an error and catch it with the error boundaries component.

You can play with the frontend here

GraphQL combined with a tool that introspect the schema and generate types give you a convenient and secure way to develop a frontend application. The strong typing and the remote procedure execution provide you something more reassuring when you manipulate data coming from the backend. This approach is not something unique and we maybe should see interesting things coming from gRPC in the future 😊.