Setup
Unfortunately, even if the actual API of GraphQL is typed, there is no automatism in that data retrieved on the client is also so.
There have long been tools such as GraphQL-CodeGen which automatically generates types based on an existing GraphQL API. My experience is that such tools have much required setup with packages that need to be installed and scripts that need to be added, and are twisted to get to work exactly as one wants.
The framework gql.tada works in a completely different way, and is written as a TypeScript plugin (new in version 5) that automatically retrieves the types from the GraphQL API when TypeScript is compiled. It allows you as a developer to have a “set it and forget it” experience, and personally I almost forgot that gql.tada is installed because it just works.
In addition, you choose which queries and mutations to type automatically or not, making it easier to apply to an existing project without having to fix thousands of bugs first.
We implemented the tool on the frontend of Mist, a web application written in React that uses @apollo /client
to communicate with a GraphQL API exposed from a backend. Here is a (simplified) example of the code of an existing query to extract a paginated list of users and display them in a table:
This is code that probably seems familiar to most people who have worked with GraphQL in React before. However, changes in Utilizer
resource from the API not automatically be reflected in the code. It creates challenges if you forget to maintain the types AllUsersQuery
and AllUsersQueryInput
when there is a change.
For example, if you remove FirstName
and LastName
to introduce a new field nome
Instead, this code will not give any error messages on it, but all GraphQL queries will start to fail because the requested fields no longer exist in the schema. And if Telefon
suddenly being returned as a Int
, will parseInt
function fail, because it expects an argument of type Strings
.
Results
Let's install gql.tada and apply it to automatically follow the types from the API. We followed start guide on the official website, which at the time of writing implies:
- Install the packages
gql.tada
and@0no -co/graphqlsp
(TypeScript plugin) - Add the TypeScript plugin below
CompileOptions.Plugins
in your existingtsconfig.json
, and point it towards your GraphQL API address. - There is no step 3 😉
Then we can rewrite the code above to completely get rid of the static types:
Now both problems mentioned above are picked up by the TypeScript compiler before the code is even built. If FirstName
and LastName
has been removed, we will bring up an error that User.firstName
and User.lastName
no longer exists. And correspondingly with Telefon
: Now we will get an error message that the function expects a Strings
, but that type is Numero
.
In other files that made use of the static types, we can instead replace them with the built-in tool types ResultOf <typeof ALL_USERS_QUERY>
and VariablesOf <typeof ALL_USERS_QUERY>
. To even more encourage reuse, one can extract the requested fields to a fragment, and then use this to define how Utilizer
type looks.
After turning on typing on more and more queries in the existing MIST code, errors started popping up that had unfortunately been overlooked in the past. This was mostly incorrect about data potentially being returned as null
or indefinido
, but in one case we discovered a different implementation of an enum on the frontend and backend -- this probably wouldn't have been discovered organically by us developers, so there gql.tada saved us from a potential (angry) customer email!
Pitfalls
Finally, I want to mention some less positive experiences I've made myself after using gql.tada for a while.
My biggest challenge is that type safety only works on the retrieved data, and not while writing the actual queries or mutations. That is, all code inside graphql ()
the wrapper needs to be written as plain text, and all the potential flaws that entails. Subject to the fact that I have configured somewhat incorrectly, one could argue that this goes a bit outside of the tool's original mission. In any case, there are own GraphQL plugins for the biggest IDEs that take care of this task, not to mention GraphiQL
or other GraphQL clients. It would be nice to have it all in one place!
If you use pipelines to build and roll out code, it should be mentioned that you have to check in and include the autogenerated env.d.ts
the type file for the code to be built on server. Whether you are using an external API, or not developing the backend yourself, it is common for changes to this file that have nothing to do with the code you are writing in right now, which can mess up pull requests. In addition, the file size is relatively large — in our case it is over 1 MB.
It is also worth reading the documentation, especially if you use fragments. These are not automatically typed, and one has to call the function readFragment ()
for the types from the fragment to be adopted. And if you have defined your own scalars in the API, e.g. a ID
or Datum
, you have to override the default configuration and import graphql ()
wrapper from there for these to be properly typed.
Conclusion
No tool is perfect, but all in all, I think the utility gql.tada delivers makes it worth trying out, especially when the stakes are so low. It is an exciting tool that has already helped us in our development and that we will continue to use on Mist and other projects in the future.