Become a GraphQL expert

We're launching a brand new course. Pre-sale is now live.

View course

Overstacked

Fri May 10 2024

The Problem with DRY Code and GraphQL

How to avoid data overfetching with GraphQL and Codegen

cover image

You're working on a front-end project which talks to a GraphQL server. Like a good developer you want full type support so you setup GraphQL Codegen.

You put all your queries inside a neat /queries directory and with every generation you get a bunch of easy to use methods or hooks like:

useUsersQuery()
useUpdateUserMutation()

This is a fantastic workflow because now you have end-to-end type safety. But whats the problem?

Well a setup like this encourages bad practices. One of the main benefits of GraphQL is being able to request only the data you need.

Before codegen you may have written a component like this.

const User = () => {
  const data = useQuery(`
    users {
      id
      name
    }
  `)

  return (
    <ul>
      {data.users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

Here we fetch a list of users to render, requesting the data we need and nothing more. This is as lean as it gets but we have no type support.

So instead we have a seperate query we can use to generate a handy typed hook.

query {
  users {
    id
    name
  }
}
const Users = () => {
  const data = useUsersQuery()

  return (
    <ul>
      {data.users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

Later in another component we need to show some more user info, but we already have this hook so we may as well reuse it. We update the original query and run codegen.

query {
  users {
    id
    name
    email
  }
}
const Contacts = () => {
  const data = useUsersQuery()

  return (
    <ul>
      {data.users.map((user) => (
        <li key={user.id}>
          {user.name} - {user.email}
        </li>
      ))}
    </ul>
  )
}

But here's the problem. Now the Users component is requesting the email field and we don't even use it. This is a simple example, but I've seen this grow into huge problems with entire fragments being requested and not used.

This completely nullifies one of the main benefits of GraphQL; keeping queries lean. So how can we stop this from happening?

The solution

Embrace repeating yourself. The DRY principal is fundamental to software development but it does not work with GraphQL queries. Each query should be written for purpose.

Avoid grouping queries together under a single folder. A queries/ directory encourages reusablity but queries should not be reused. Instead keep queries close to the components which use them.

Inline queries

For codegen you could even write queries inline and still have full type support. See plugin gql-tag-operations-preset which generates types based on matching template literals.

// With codegen and gql-tag-operations-preset
// this returns a typed response
const [res] = useQuery({
  query: gql(`
    query savedPosts {
      savedPosts {
        id
        post {
          id
          body
        }
      }
    }
  `),
})

Once types are generated this would show typescript hints just as you would receive with seperate hooks or a generated sdk.

Query with type support

This workflow is nice because it encourages lean queries written for purpose.

Separate queries

Another option is to organise your components along with their queries.

containers/
  UserList/
    index.ts
    queries.graphql
# containers/UserList/queries.graphql

query UserList {
  user {
    id
    name
  }
}
// containers/UserList/index.ts

const UserList = () => {
  const res = useUserListQuery()

  return (
    ...
  )
}

This solution may be preferable if you like to work with graphql files. Although be careful to keep your queries lean and avoid data overfetching.

Of course you can still work with features like graphql fragments for reusability of query blocks when needed. But be careful how you design this to avoid overfetching.

Thanks for reading.


You can get more actionable ideas in my popular email newsletter. Each week, I share deep dives like this, plus the latest product updates. Join over 80,000 developers using my products and tools. Enter your email and don't miss an update.

You'll stay in the loop with my latest updates. Unsubscribe at any time.

© Copyright 2024 Overstacked. All rights reserved.

Created by Warren Day