Redundant "Through" Relationships in GraphQL


This is part of a series about decisions my team made with our GraphQL schema. Check out that post for context, or to read about other decisions we made.

Let’s say you’re modeling the users in a set of companies. Each company has offices scattered across multiple cities, each of which is split into departments that contain a collection of users. It might be tempting to model your schema like this to keep it lean:

Schema

type User {
  name: String!
  department: Department!
}

type Department {
  name: String!
  office: Office!
}

type Office {
  city: String!
  company: Company!
}

type Company {
  name: String!
}

type Query {
  user(id: ID!): User
}

This schema is nice and small, and doesn’t include any redundant ways to access information. It probably maps closely to how your database would be set up in this situation.

Let’s look at how we would query for the Company a certain user belongs to.

Request

{
  user(id: 51) {
    name
    department {
      office {
        company {
          name
        }
      }
    }
  }
}

Response

{
  "data": {
    "user": {
      "name": "Michael Scott",
      "department": {
        "office": {
          "company": {
            "name": "Dunder Mifflin"
          }
        }
      }
    }
  }
}

The name of the company this user works for is 7 layers deep in the response! The only time I want seven layers is when I’m eating a Mexican bean dip. It’s fine to have a response like this occasionally, but if this is a common access pattern, I’d add a company field directly to the User type. This looks much better:

Schema

type User {
  name: String!
  department: Department!
  company: Company!
}

# Rest of types unchanged from previous example

Request

{
  user(id: 51) {
    name
    company {
      name
    }
  }
}

Response

{
  "data": {
    "user": {
      "name": "Michael Scott",
      "company": {
        "name": "Dunder Mifflin"
      }
    }
  }
}

Our API includes these redundant “through” relationships wherever possible. There’s a tiny bit of overhead in the server-side codebase to maintain all these relationships, but the ease of being able to go to any relationship in one “hop” more than makes up for it. I’d recommend that anyone building a GraphQL API at least consider this tradeoff and decide for themselves whether it’s worth it.

© 2024 Sean Gransee