7
votes

I'm trying to figure out how to implement bottom up filtering in a parent-child relationship using GraphQL.

Consider the following basic type hierarchy modelling a parking lot and available parking spots:

type ParkingLot {
  id: Int!
  spots: [ParkingSpot]
}

type ParkingSpot {
  id: Int!,
  occupied: Boolean!
}

I want to implement my resolvers in a way that enables me to retrieve all parking lots which have at least one free parking spots.

query AllParkingLotsWithStatus($status : Boolean = false) {
   ParkingLot {
     id 
     spots(occupied: $status)  {
       id
     }
   }
}

The specific implementation of GraphQL that I'm using is PHP based and available here but any JS based implementation would be really helpful in pointing me in the right direction. I know there are libraries out there that support filtering using "where:" parameters but I would have to implement this mechanism myself as the library i'm using does not offer it out of the box.

One idea that I explored was to simply write a JOIN statement inside the resolver for ParkingLot, but I feel this is not in the spirit of GraphQL.

Another idea that I thought of is rewriting the query such that it retrieves ParkingSlots first and only gets their parent if their status is available. However, this might be computationally intensive as there will be less parking lots than parking slots so navigating the relationship from the parent to the child would probably be less computationally intensive.

What approach would you suggest that does not boil down to changing the type hierarchy (i'm in no position to be able to do that). Is there, perhaps, a way to reject the parent node as soon as some condition happens in the resolver of a child or would this be considered outside the scope of GraphQL implementation and more like a feature of the actual library being used? I suppose since some libraries out there support filtering using "where:" clauses this must be possible and shouldn't be complicated.

1

1 Answers

2
votes

Couple of points.

The preferred way to design your schema would be something like:

type Query {
  parkingLots(where: ParkingLotFilter)
}

input ParkingLotFilter {
  isFull: Boolean
  # other conditions
}

While you can add an occupied argument to spots, it's very unintuitive to have the parkingLots query only return parking lots that have free spots when that argument is provided. If I specify that argument, I expect to get all parking lots, with the spots field on each one limited to spots that are/aren't occupied. In other words, a filter for a field should only impact that field's resolution, not the resolution of the parent field.

That brings us to the second point -- your problem could simply be resolved client-side. It may be sufficient to expose the occupied argument on spots and leave it at that. A client would then get a list of parking lots, some of which may have an empty array for the spots field. The client can then just filter the parking lots list based on the length of the spots field to get a list of lots that are or aren't full.

If that's not acceptable, then the easiest way to handle this issue is to do a join as you suggest. There's nothing "not in the spirit of GraphQL" about doing it this way -- many tools like join-monster and postgraphile take effectively the same approach. By using a join, you can get the spots for each parking lot and return them as part of the parking lot object that's returned in the resolver. As long as the object has a spots property, that property's value will be used to resolve the spots field (provided you don't write your own resolver).