Fully typed API responses using GraphQL
Tags: react, graphql, typescript, vscode
Published: 2021-03-16 17:23:58

At my workplace, we were setting up the environment for a brand-new app and decided to go with typescript to get the most out of the static analysis it provides. We have a standard architecture, a REST API, and react/react-native frontend.

Furthermore, we were able to make sure that the whole app is type-safe except for the part where we consume the API responses. I had to manually write the types for the API responses. The biggest problem with this approach is there is no way we can ensure the responses are type-safe because we are just "assuming" their types.

Then I wondered what would happen if we have [GraphQL](https://graphql.org/) in the picture and set out on a journey. If you don't know what GraphQL is, it's a Query language for your API, where you define what your API can provide as strictly typed schema and clients will consume a subset of it.

I decided to build a POC using GraphQL with a complete type-safe frontend. You can access the full code(server and client) [here](https://github.com/santosh898/typesafe-graphql).

## Server-side

I won't be dealing with the server-side aspect of this, and I won't go deep into GraphQL as well. The following is the schema of my API.

> the server is written using node+typescript+apollo-sever

### Schema
```
type Book {
  id: Float!
  title: String!
  subtitle: String!
  author: String!
  published: String
  publisher: String
  pages: Int!
  description: String!
  website: String!
}

type Query {
  getBooks(limit: Int): [Book]
  getBookDetails(id: Float): Book
}
```
In the above schema, `type Book` is a resource, and `type Query` is where we define what kind of queries are supported.`getBooks` will respond with an array of `Books` and `getBookDetails` will respond with a `Book` for the given `ID`.

## Client-side

So we have the following problems to crack.
* Connect front-end to GraphQL.
* Get Fully typed responses automatically.
* IntelliSense when writing the queries.

### Connect the front-end to GraphQL

I created a GraphQL powered react-app using [Create Apollo app](https://apolloapp.org/) by running
```
yarn create apollo-app client
```
It has out-of-the-box support for `.graphql` files and the boilerplate code to connect to the backend.

> See client/src/index.tsx

But later I found that the template is pretty old and upgraded `graphql` and migrated from `react-apollo` to `@apollo-client`.

> I used the create-apollo-app for generating the server codebase as well.

We can consume data by using the `useQuery` hook from `@apollo/client` like this.

> client/src/queries/getBooks.ts
```
import { gql } from "@apollo/client";

export const GET_BOOKS = gql`
query getBooks($limit: Int) {
  getBooks(limit: $limit) {
    id
    title
    subtitle
    author
  }
}
`
```
> client/src/ListBooks.tsx
```
import { useQuery } from "@apollo/client";
import { GET_BOOKS } from './queries/getBooks.ts'

const ListBooks: React.FC<{}> = () => {
 const { loading, error, data } = useQuery(GET_BOOKS,{
    variables: {
      limit: 5,
    },
  });
...render data
}
```
It works but the data isn't fully typed yet.

### Get Strongly typed responses automatically

To avoid writing the types for the responses manually we are going to use [GraphQL Code Generator](https://graphql-code-generator.com/docs/getting-started/index).

graphql-codegen is a CLI tool that generates types automatically from the provided GraphQL Schema. They have a lot of plugins and options for generating the types for both frontend and backend.

**By using this we can have the server-side written GraphQL schema as a single source of truth for the whole application.**

The setup is pretty straightforward. Refer to the [Installation page](https://graphql-code-generator.com/docs/getting-started/installation):

```
# install the cli as a dev-dependency
yarn add -D @graphql-codegen/cli

# Step by step walkthrough initialization
yarn graphql-codegen init
```
The above code adds relevant dev-dependencies based on our selection and creates a `codegen.yml` file at the project root. My `codegen.yml` file looks like this.
```
overwrite: true
schema: "http://localhost:8080/graphql"
documents: "src/**/*.ts"
generates:
  src/queries/typedQueries.ts:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"
```
I'll walk you through the options.
- **schema** - the URL for the schema. Can be a file, function, string as well. See the documentation.
- **documents** - where to search for the GraphQL queries and fragments. I asked it to search in the `.ts` files
- **generates** - the target file path.
- **plugins** - automatically added based on the options selected in the `init`
  - **typescript** - bare minimum plugin to generate types.
  - **typescript-operations** - to generate types for the [GraphQL Operations](https://graphql.org/learn/queries/)
  - **typescript-react-apollo** - to generate typed hooks for the queries written and other support for the @apollo/client.

Now after running `yarn generate` it'll generate the file `typedQueries.ts`. And I updated my component to use the generated `hook`.

```
import React from "react";
import { useGetBooksQuery } from "./queries/typedQueries.ts";

const ListBooks: React.FC<{}> = () => {
  const { loading, error, data } = useGetBooksQuery({
    variables: {
      limit: 5,
    },
  });
...render data
}
```
What changed here? we are not importing the query anymore, the generated types will do that for us. and guess what? the `data` is fully typed.:confetti_ball:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f7zt0jwtli07ojpp6jg0.png)
 
Instead of having to run `yarn generate` every time we change a query, we can run the codegen-cli in watch mode as well.See [Documentation](https://graphql-code-generator.com/docs/getting-started/development-workflow).

> **Note**: If you are going to use node for the backend, codegen-cli will be helpful for generating types from Schema so that you don't need to define the types two times. Refer to server code in my codebase for reference.

### IntelliSense when writing the queries (in vscode)

The [Apollo VS Code extension](https://marketplace.visualstudio.com/items?itemName=apollographql.vscode-apollo) provides an all-in-one tooling experience for developing apps with Apollo.

We will get the syntax highlighting by just installing the extension. But to have IntelliSense we have to add a config file to the project.

> client/apollo-config.js
```
module.exports = {
  client: {
    service: {
      name: "my-graphql-app",
      url: "http://localhost:8080/graphql",
    },
    excludes: ["**/src/queries/typedQueries.ts"],
  },
};
```
after adding this config, the extension downloads the schema from the URL and provides the IntelliSense when you write the queries.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4jhy8rknotismds6sdc7.png)

One last thing! when I updated the schema, the extension didn't pick that up, so I had to run `reload schema` manually. (`ctrl+shift+p` to open command palette)

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3qpuoekgn4njmjo074oj.png)
  
I don't know if there's an option for this to happen automatically. I didn't go deep into the extension [documentation](https://www.apollographql.com/docs/devtools/editor-plugins/).

That's it. Now I have a strong type system for API calls plus added benefits of GraphQL.

This is my first ever post. If you are still here, thank you for having the patience to stay this long.