How do I prevent overfetching with graphQL

187 Views Asked by At

I am working with a project that uses graphQL. I understand that graphQL apis are supposed to prevent overfetching (and underfetching) of data but I do not understand how this is supposed to work in real life.

Say I have a query to get employee data like this:

const getEmployeesQuery = `
    query GetEmployees($aliases: [String]) {
        getEmployees(aliases: $aliases) {
            personId
            username
            fullName
            businessTitle
            email
            trainings
            location {
                id
                name
                buildingCode
                timezone
                city
                country
            } 
        }
    }
`;

export default getEmployeesQuery;

When this query is executed, our backend will get all this data from a variety of sources. However suppose that the request does not include trainings in the request. What I don't understand is how can the backed identify that this field is not there and then NOT make the operations to get that data?

Is the graphQL backend supposed to parse through this string to determine what the user requested at every level? That seems unrealistic.

Ideally if I don't include trainings (or any particular field) then the backend should be optimized to not retrieve that data, right? The query I posted here is relatively simple but we have some that are extremely nested. The frontend will only end up receiving data included in the request but shouldn't we be preventing the backend from getting data not included in the request body? That is what I don't understand.

1

There are 1 best solutions below

0
Nic On

You're correct; one of the main benefits of GraphQL is that it doesn't over-fetch.

From client to server, this is easy to prevent. Request the fields you want, and the client will receive them from the server. So just remove trainings from your Client-side request. However, that doesn't prevent GraphQL from hitting your database for every field in your request, including trainings.

What you need is to pass the info data at the Resolver level. I am currently using Nestjs with GraphQL & knex so my implementation looks like this:

Focus on Info

import { Args, Info, Query, Resolver } from '@nestjs/graphql';
import { UsersService } from './users.service';
import { User } from 'src/common/models/user.model';
import { UsersArgs } from 'src/users/arguments/users.args';
import { Role, Roles } from 'src/auth/auth.role.decorator';

@Resolver(() => User)
export class UsersResolver {
  constructor(private readonly usersService: UsersService) {}

  @Query(() => [User])
  @Roles(Role.Inner)
  async users(@Args() getUsersDataArgs: UsersArgs, @Info() info: any): Promise<User[]> {
    const requestedFields = this.extractRequestedFields(info);

    return this.usersService.find(getUsersDataArgs, requestedFields);
  }

  private extractRequestedFields(info: any): string[] {
    const selections = info.fieldNodes[0].selectionSet.selections;
    return selections.map((selection: any) => selection.name.value);
  }
}

You'll now have access to the requested fields in your service and can now use them something like this:

const selectRequest = /* use requestedFields to create dynamic select query here */
const query = this.knex
        .select(selectRequest)
        .from('foo')
        .where('bar', bar)

Hope this helps!