Project setup with react-query with Axios, typescript & code generator for simplified graphQL requests.

One of the misconceptions I had about GraphQL was that its an entirely different shift from how we transported data using REST. I thought, using normal "fetch" calls wouldn't work with GraphQL (oh.. how naive I was). But soon as I started playing around with GraphQL I realized it's just a beautiful abstraction over how we fetch data. I mean beneath all that queries and mutations still lies the same mechanism. Over HTTP, it's just a simple HTTP POST method with all queries in its body. I mean it is more than that, but it helped me to understand GraphQL more closely. (this paragraph is not related to anything below, just wanted to throw it out there :p )

In my first project with GraphQL, I worked around with apollo-client without any code generator setups. That was a lot of mess. Though apollo-client was good in many aspects we found its configuration and "refetch policies" a bit tedious to work with and having all the developers learning to use apollo-client meant they would have to learn another library or packages to work with REST. So, we chose to set up our new project with react-query. Since react-query doesn't care about how we fetch data and just focuses on providing out-of-the-box features, it works perfectly well with GraphQL as with REST APIs. So, We decided to use react-query with axios.

After the mess that we created by writing our own typings for data, we learned about graphql-code-generator which generates all the types & hooks automatically just by looking at the schema and graphql queries.

In this blog, I am planning to explain all the things that I did while preparing the setup for our project so that it will serve as documentation for myself in the future. That said, I am not going into too much details.

Step 1:

As clearly mentioned in the doc here, I ran automatic installation setup.

yarn graphql-codegen init

This generated a codegen.yml file which provides all the required configurations for the code-generator to generate graphQL typings. As I said, I am not going into details as the step is pretty self-explanatory.

Step 2:

The first step sets up our application for a basic auto type generation. However, we needed to have functions or hooks automatically setup with react-query and axios. Amazingly, the code-generator comes with this plugin. So I added the plugin.

yarn add @graphql-codegen/typescript-react-query

Step 3:

Then I changed my codegen.yml file:

overwrite: true
schema: ${VITE_SCHEMA_PATH}
documents: 'src/**/*.graphql'
watch: true
generates:
  src/generated/graphql.ts:
    plugins:
      - add:
          content:
            - '//This Code is auto generated by graphql-codegen, DO NOT EDIT'
            - '//You can update the queries or mutations in *.graphql to generate any new changes.'
      - 'typescript'
      - 'typescript-operations'
      - 'typescript-react-query'
    config:
      skipTypename: true
      fetcher:
        func: './axiosHelper#useAxios'
        isReactHook: true
  ./graphql.schema.json:
    plugins:
      - 'introspection'

Though most of the configurations were already generated during initialization, I added typescript-react-query as a plugin and added some configs for it.

skipTypename: true

Without this config, __typename field was generated on every type. And I was forced to use it while defining types for passing the data as props. This was my personal choice.

 fetcher:
        func: './axiosHelper#useAxios'
        isReactHook: true

Since we were planning to use react-query with axios, I used this config to create a custom axios setup. And using it as a hook would allow me to use states if required so I chose to create the custom fetcher hook

 - add:
          content:
            - '//This Code is auto generated by graphql-codegen, DO NOT EDIT'
            - '//You can update the queries or mutations in *.graphql to generate any new changes.'

Using this enabled to add the contents on top of the generated file. However to enable this feature, I had to add another package as a dev dependency:

yarn add -D @graphql-codegen/add

Step 4:

Now as I completed setting up the codegen.yml file, now all I needed was to create my custom hook with axios.

....
generates:
  src/generated/graphql.ts:
....
...
      fetcher:
        func: './axiosHelper#useAxios'

If you look at my codegen.yml file, you can see that I chose the directory /generated on file graphql to populate all the typings generated by the code-generator. So the './axiosHelper#useAxios' on func field meant the generator will be using useAxios (named export) hook on /generated/axiosHelper.ts file. Therefore on /generated directory I created an axiosHelper.ts file with the following code:

import axios from 'axios';

export const useAxios = <TData, TVariables>(
  query: string
): ((variables?: TVariables) => Promise<TData>) => {

  const url = import.meta.env.VITE_API_URL; // Your server URL here

  return async (variables?: TVariables) => {

    return axios.post<{ data: TData; errors: unknown }>(url, { query, variables }).then((res) => {
      if (res.data.errors) throw new Error(JSON.stringify(res.data.errors));
      return res.data.data;
    });
  };
};

Step 5:

Well, That was all that is needed to setup react-query with axios with graphql-code-generator. But I wanted to make sure that the generator is updating the generated functions or hooks while I am adding/updating queries or mutations on *.graphql file within src directory. So, I changed the script on my package.json file to run the server and code-generator in watch mode concurrently. You could, however, achieve that by running the code-generator on watch mode in a separate terminal.

yarn add -D concurrently

package.json

"scripts": {
    .....
   "graphql:codegen": "graphql-codegen --config codegen.yml -r dotenv/config",
    "dev": "concurrently \"yarn start\" \"yarn graphql:codegen\"",
   ...
  },

Since I planned to import the schema path from the .env file I had to embed -r dotenv/config on the graphql:codegen script if you use the path directly then you can simply remove that and it works just fine.

This setup was enough to get the project running, however, we ran into problems when we wanted to upload files. With appollo-client all you had to do was add this package and set it up accordingly mentioned in its documentation. However, there were not many resources available to set up file upload, so I created my own. I will be writing about this in my next blog (still not written).

(I am choosing not to write conclusions). Bye!