Hi @alawi
I think I should write a blog post or something about our discoveries with GraphQL as I’ve found it quite difficult to find similar opinions about GraphQL: the sentiment seems to me to be along the lines of “use GraphQL… end of discussion” on the internet, without much nuance.
GraphQL penalties:
On developer-overhead and complexity… to expand a REST API server with more functionality—or even to get it going in the first place—there’s not a lot of incremental wiring one has to do to conform to the rules of the server. With something like Apollo Server though, you have the schema itself, also resolvers, also models, also data-loaders. Knowing what’s wired up to what might seem like you could learn it and then get it, but it’s actually a lot to hold in a brain at once, especially because it’s not the same case for all uses—sometimes resolvers aren’t needed because they come from the data itself, sometimes resolvers are needed to deal with it, sometimes resolvers call models calling data-loaders calling models. With REST it’s all there in your endpoint. The complexity and boilerplate lead to more dev time for sure… even the simpler things like schema is stuff you don’t have to do with a typical REST endpoint
On authorization
GraphQL is easy to add authorization to at the mutation level—just like a PATCH
/PUT
/DELETE
—but adding it at the query level is… I would actually say impossible. Hear me out… you’re traversing a graph, so you can enter a resolver via multiple ways; what if you need to lock down the access to the authorization of the user who is requesting the entity in one of those ways but via the entity itself in another of those ways? This might sound crazy, but it’s actually not unreasonable, especially when you consider user-administration where one might have a different set of permissions for themselves compares with a user they can administer. Any solution to this makes my brain explode. All authorization examples I’ve seen are more trivial… generally have no additional logic on them like “user has x permission on y entity”
On optimization
In a REST endpoint, you can highly optimize the endpoint for the specific task at hand. Here’s a weird example to obfuscate our use-case Let’s say the app is for a heat-ventilation/air-conditioning company and for a given user of the app, you want to see all of the heat exchange date settings across the office buildings they have access to… something like:
query {
user {
officeBuildings {
id
name
hvacSystems {
id
name
settings {
heatExchange {
startDate
endDate
}
}
}
}
}
}
Using data-loaders in GraphQL, you’d probably end up:
- Getting the user requesting the service
- Finding all officeBuildings that the user has access to
- Finding all hvacSystems across all of the office buildings
- Finding all heatExchange settings across all of the different hvacSystems
It’s a necessary waterfall. In a REST endpoint, that could be done in 1 step… in reality I’d probably put the first step into middleware, so instead it’s a 2-step process in REST. You could add multiple entry points to overcome this if it’s a common use-case in GraphQL, but then you’re increasing the complexity even further… maybe authorization too
On unnecessary/long-winded data-structure migrations:
This one might be more self-evident if you think about the case when the schema has to change as business-logic changes. e.g. Maybe in the case above, hvacSystems no longer exist as an entity, maybe it was the wrong way of conceptualizing your business, and instead you have independent components that are serviced. You need a lot of coordination with clients to rip that out. It might be work in REST to do the same, but maybe not… a lot of the time in REST you give a request and it tells you some subset of data. For times when the data structures being returned from REST don’t change, you could rewrite the way your endpoint functions internally while still maintaining the same data response. This might sound fringe, but there have been multiple migrations over the last year like this that cost a fair bit of extra development and backwards/forwards compatability woes for our mobile app, since you can’t control what versions of a mobile app are out there. Often change doesn’t even need to happen on clients but since there’s a schema mapping clients to a server’s resolvers, it can be necessary (or really hacky/complex to work around it).
I like the simplicity and flexibility of method calls in Meteor. We used REST before GraphQL. I treat REST more like an RPC server if we have control over the clients. Anti-pattern, but I’m anything but puritanical.
Interesting having Meteor as the backend/build! How do you distribute/serve it all?