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.
The parent argument of the resolver is simply the result returned by the resolver at the previous level. The call to the root level resolver receives a null in this argument.
Searching and filtering is a standard part of any GraphQL API. In this article, we'll learn how to add filtering capabilities to your API by walking through a few real-world examples.
A resolver is a function that's responsible for populating the data for a single field in your schema. It can populate that data in any way you define, such as by fetching data from a back-end database or a third-party API.
The __typename field returns the object type's name as a String (e.g., Book or Author ). GraphQL clients use an object's __typename for many purposes, such as to determine which type was returned by a field that can return multiple types (i.e., a union or interface).
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With