Hacker News new | ask | show | jobs
by ralphstodomingo 1103 days ago
> simply replacing REST adds resolver complexity [...]

To put it simply, even with REST, you're already dealing with the concept of "how does clients get a handle of a particular entity?" With REST, the answer is usually muddled by client data fetching mechanisms and backend architecture that you've opted to use - so just because two projects are doing REST does not mean they're handling these entities in a similar, understandable nature and it requires a lot of background context, same with auth.

graphql makes all that a deliberate matter instead. So take whatever's in your db, decide which fields are you going to expose, and write resolvers that return just that - you can't get any more foolproof than that, it's basically the same thing you do with REST, just without all the client-side fumbling and coordination.

But what I'd argue the thing it does best, is that with REST, you'd probably write code for handling Entity A, and if Sub-Entity A and B rely on Entity A, you'd probably write specific code to ensure the right entities are returned in your endpoints. With graphql, you just reference that entity and it's resolved automatically, because you have written the resolver for that entity already. Resolvers have access to the parent entity that's referencing it, so if Sub-Entity B has stricter controls in what it should expose, the Entity A's resolver can be written to accommodate that (see directives among other ways for how to scale this better).

As for auth, whatever middleware you're already using in your REST endpoints can also be reused one way or another for the gql endpoint, no issue.

> this hyper graph thing is only valuable with other graphql services.

Even without other graphql consumers/services, there's huge value in not having to write multiple ways of handling a certain entity anymore, among other things.

> So, I guess graphql can be good if you marry it and go 100% all in?

All of my recent work related to graphql happened in projects that incrementally adopt it - there's no requirement whatsoever to fully commit to it 100%. It definitely helps that you can reuse whatever your REST endpoints use to comprise code that builds up towards resolvers, like util/helper fns. Nothing stops you from serving the same entity in both REST and graphql forms, heck there's specific usecases that benefit from it (i.e. maybe your mobile app can't keep up with it yet, and you're trying it out for web for now etc)

Say, in such kinds of projects, what I can recommend is identifying a subset of entities that your backend serves that you think (at least superficially) will benefit from it e.g. a relatively new entity that is essentially WIP across releases, and you'd benefit from at least not having to redo the entire REST API endpoint subset for this entity whenever you're making drastic changes, or a particularly old one that could use a rethinking anyway, among other use cases. After a couple of releases you'll get a feel for how it works, and then it'll be enjoyable to port more of the entities later on.

1 comments

Thanks, this is really informative.

> decide which fields are you going to expose, and write resolvers that return just that

To be clear this means writing custom resolvers? That seems like the most sane way to use graphql, should I ever revisit it.

What’s the logical authorization entity? In rest, it’s (typically) the endpoint itself. What is it in graphql? Does a specific entity resolver have authorization? What does it look like in pseudocode?

How hard is it to write custom resolvers that also produce efficient sql? A gql query can be arbitrarily complex (ie nested), no? How to curb that complexity in practice?

Hmmm I'm not aware if the word 'custom' applies to resolvers - to an extent, you're always going to write them out as such for optimal benefit, can't imagine a non-custom one if that's what you meant.

> What’s the logical authorization entity? In rest, it’s (typically) the endpoint itself. What is it in graphql? Does a specific entity resolver have authorization? What does it look like in pseudocode?

You can have both API-wide and resolver-defined auth. In your graphql server config, there's a `context` config option you can pass a function to, and it has access to the whole HTTP request (typically, depends on the integration but I assume all of them treat it the same), it's where you'd probably e.g. check for auth headers and run them against db or the cache layer for auth etc. You can already throw errors and whatnot within this function, so that's the API-wide part.

The returned value of this context function is then included in the params of any executing resolvers, so you can have that resolver throw if e.g. the user stored in context does not have the sufficient roles to access this entity.

There's also directives: say you want to have an `@auth` directive that you can just slap on typedefs that makes auth logic reusable across a subset of entities, but you don't want to handle it on both API-wide and resolver levels, you just write a transform fn, register it in the config, and put that directive on the schema itself.

> How hard is it to write custom resolvers that also produce efficient sql? A gql query can be arbitrarily complex (ie nested), no? How to curb that complexity in practice?

Yes, a gql query can be complex and nested, but you only need to write the resolver in a way that all the ways you need to resolve that entity are taken into account. A query resolver returning one instance of Entity A via their unique ID in most cases is enough, for example - it does not matter how deep into the gql query the entity appears, graphql will do the heavy lifting and refer or run every resolver fn until all entities in that query have been resolved.

The next question is, that means graphql will hit the db one-to-many-times depending on the schema, and yes, that'll happen, but there are again, granular ways to handle that depending on the integration - Apollo at least lets you configure your own in-memory cache or use Redis for example, and be able to cache results in the schema-wide, resolver and response levels.