# Improving Code Readability Using GraphQL Fragment Colocation

## TL;DR

<div data-node-type="callout">
<div data-node-type="callout-emoji">☝</div>
<div data-node-type="callout-text">It will eliminate over-fetching, improve readability, and make it easier to maintain</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">✌</div>
<div data-node-type="callout-text">It ensures that only necessary data is queried and that your code remains modular and efficient</div>
</div>

## Background

At my job, I worked on a Next.js application that used GraphQL. We also used [GraphQL codegen](https://the-guild.dev/graphql/codegen/docs/getting-started) for TypeScript types.

Initially, a senior developer with a mobile development background introduced a module-based pattern, which led us to create models for each GraphQL type.

### The Initial Approach: Using Models

This is a simplified version of the `UserModel`:

```ts
export class UserModel implements User {
  public readonly id: Scalars['ID'];
  public readonly firstName: Scalars['String'];
  public readonly lastName: Scalars['String'];
  
  constructor(user: User | undefined) {
    this.id = user?.id ?? '';
    this.firstName = user?.firstName ?? '';
    this.lastName = user?.lastName ?? '';
  }

  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
}
```

This `UserModel` implements auto generated `User` type. (So eventually includes all user tables fields.) And defined some private methods.

And for the GraphQL, we kept queries in a single folder:

```graphql
// src/graphql/queries/user.graphql

query User {
  user {
    ...UserDetails
  }
}

fragment UserDetails {
  id
  firstName
  lastName
  email
  address
  phoneNumber
  # Fetching all fields
}
```

We were using fragments but read all fields to make it easier to reference them in the models.

And this is how we refer to user’s full name:

```ts
const user = new UserModel(graphql_result);
user.fullName();
```

For this example, using only `firstName` and `lastName` is straightforward. However, in real applications, models tend to be more complex.

Since we were querying all fields and including them in the models, we were unsure which properties were necessary in different parts of the app. As a result, we often ended up over-fetching data, which negated the benefits of GraphQL. 🫣

## The Shift: Seeking a better approach

After a few years, the original project was canceled. However, I got the opportunity to build a new app from scratch with the same tech stack. The senior developer had left, and I became the lead front-end developer. 😎 This time, I decided to get rid of the module-based pattern entirely.

Initially, I wrote helper functions:

```ts
// src/helpers/user.ts

const getFullName = (user: User) => {
  return `${user.firstName} ${user.lastName}`;
};
```

However, I still kept using the generated `User` type. Also, GraphQL queries are still in a single folder, leading to the same inefficiencies.

Then, I got a advisor who introduced me to the power of **GraphQL fragment colocation**. ✨

## What is GraphQL Fragment Colocation?

GraphQL fragment colocation is the practice of defining fragments near the components or functions that consume them. Instead of keeping all GraphQL queries in a centralized location, colocating fragments ensures that only the necessary fields are queried and makes the code more modular and maintainable.

For more details, check out:

* [Apollo Docs on Colocating fragments](https://www.apollographql.com/docs/react/data/fragments#colocating-fragments)
    
* [Advisor's note (Japanese)](https://github.com/Quramy/gql-study-workshop/blob/main/chapters/03_colocation.md)
    

## Implementing Colocation: A New Approach

We decided on new rules based on my advisor’s advice.

New Coding Rules are:

* Write fragments in the same file as the functions/components using them
    
* Query only the fields needed in that file
    
* Avoid using generated types directly
    

Example:

```ts
// src/helpers/user.tsx
export const USER_FULLNAME = graphql(`
  fragment UserFullName on User {
    firstName
    lastName
    middleName
  }
`);

export const getFullName = (user: {
  firstName: string;
  lastName: string;
  middleName: string;
}) => {
  return `${user.firstName} ${user.middleName} ${user.lastName}`;
};
```

Now, the helper function defines its required fields explicitly via a fragment. This makes it clear which properties are used and avoids unnecessary data fetching.

Or we could use `DocumentType` which is generated from `codegen`:

```ts
import type { DocumentType } from "~/__generated__";

export const getFullName = (user: DocumentType<typeof USER_FULLNAME>) => {
  return `${user.firstName} ${user.middleName} ${user.lastName}`;
};
```

\* Note that we are not using `User` type here.

When calling the function, we ensure the required data is passed:

```ts
// src/page/user.tsx
import { getFullName } from '../helpers/user';

const { data } = useQuery(
  graphql(`
    query UserFullNameQuery {
      user {
        ...UserFullName
      }
    }
  `)
);

const fullName = getFullName(data.user);
```

## Conclusion

By colocating GraphQL fragments and only fetching necessary fields, we achieve:

* **Improved Readability:** Queries are easier to understand and maintain.
    
* **Reduced Over-fetching:** We fetch only the data we actually use.
    
* **Better Maintainability:** Changes to data structures remain localized.
    

This approach makes GraphQL development cleaner and more efficient, ensuring that our frontend remains both performant and easy to work with. 🥳
