#Wrapping head around nest-way of graphql (mercurius) auth and cache

1 messages · Page 1 of 1 (latest)

dense bluff
#

Setting up mercurius with fastify, auth, and cache over redis is very simple in a vanilla project, but then we have much bigger problems that nest looks to solve. As someone new coming in, though, it is quite hard at the moment to imagine what the nest way of doing it is.

  1. it seems we must collect all cache instructions at the root, which would imply knowing ahead of time everything about our application. Moreover, each module could be served separately over any network protocol - maybe in that case it is easier because they could be cached centrally within themselves and that is ok because their domain is small enough that it does not matter that you centralise these instructions in each service?
  2. graphql-shield is a similar pattern for authorization with similar patterns, so I am looking at mercurius auth instead but the abundance of nest's pipes and validators and guards is a little overwhelming at the moment. Are they to be used, wrapped around mercurius auth, or what is the deal?

I hope I'm making my current state clear - it is of excitement at what nest helps me solve, but also the relative aimlesness when it comes to some of these decisions. A well pointed finger here could really save me months - I am sure most that read here could relate to the feeling so if you know something.. Have mercy and spill a bean 😉

obsidian grotto
#

What is it you are putting in the cache?

What kind of authorization are you looking for?

dense bluff
#

Putting in cache everything I can with a single digit second TTL. Most of the data served will be public. For user-scoped requests, I think it is too advanced for me to solve yet and I will curtail that sort of thing one way or another. Probably by not caching any full request that gets resolved by a non cached resolver (user session specific) at any depth.
Authorization simple rbac would be a great start.

Both seem doable at the application bootstrap level (fastify + GraphQLModule.forRoot()) but this doesn't seem like the nest way of doing things, and hence my question. I get no benefit of modules or coupling decorators to the matter at hand.

obsidian grotto
#

So, what requirement needs you to cache everything? Sounds like premature optimization. Also, caching GraphQL server-side isn't a best practice. Look into that.

RBAC, ok. You can add guards to resolver methods to get simple RBAC. That's how you'd set up simple authz with GraphQL and Nest.

I worked on a POC to integrate CASL, where the query in the request is introspected to see what is being requested along with JWT authn (getting the user's id) to then decide in a global way how to handle the user (let her pass into the system or not). It was using a global guard and I'd use @Public on the resolver to make it available to anyone not logged in. I got so far as to see it would work, but nothing more and it was very kludgy. 😊 But, I knew it was possible.

dense bluff
#

We have serious traffic, and an unoptimised GQL api will fall over. HTTP caching GQL requests is not fantastic, but we seriously looked into tRPC and edge http caching as an alternative to graphql, and it has a much better / simpler caching story due to no nesting and field resolvers, but we decided to stick to graphql for decoupling via federation and later a gateway if necessary.

So our answer to that is instead of doing it on the edge, we simply horizontally scale our api in k3s, and attach redis to persist both short-lived and long-lived cache (decided through "some magic way that is hopefully not stating all of that at the root level of our application, ruining the decoupling), like this: https://github.com/mercurius-js/cache (see "storage" option).
This reduces the need for us to babysit user-specific fields, at least repeated lookups will be easier to carry out. With Rest or tRPC, we would simply make a gentlemen's agreement to not include user-specific fields into cached routes, crank their TTL on the edge, remove caching for any user-specific queries and bob's your uncle. With apollo-server, there is the @cacheControl directive, which I guess is something you can apply with the @Directive decorator, but with mercurius the caching story is far better but as per the question seems to beg being assembled globally at the top level

#

RBAC is just the start of course, it is a naive example but with all of the above context I need to first figure out how I can decouple the mercurius' cache configuration and apply it on the query/mutation resolver level, and the field resolver level. If that can actually work, then the real work will continue as we will need more complex permissions checks. I've made progress on learning Nest since starting this topic, but i'm still a ways from understanding if it's possible

tame raptor
dense bluff
#

That is super cool, thanks for the heads up! Going to look into it, even if we don't end up using it there is a lot to pick up here

dense bluff
#

This is actually low key amazing haha, thanks again shared it around as well. It seems to solve a lot of our problems in a more traditional way. Instead of doing it through metadata, a simple approach here would be to just implement it on the service level - undefined cache options run db queries as normal, otherwise they get passed to bento wrapping the database getter. query/mutation resolver level call the service methods with cache config. That achieves the decoupling between modules, and uses encapsulation within a module between the resolver and its service. This is a great start, still lots to unpack and to figure this out for the field-level resolvers and for authorization like you suggest. Cheers!!

tame raptor
#

Yup, manual use on the service level is where I plan to use it as well. It could be used in a mercurius plugin (the mercurius-cache uses async-cache-dedupe, which is very similar, minus the tiered cache - it should be quite easy to adapt the plugin for bento), but in general I find that I keep using few entities over and over (e.g. a product is used all over the place - cart queries/mutations/operations, order, listings, details etc.).

Even if I configured all product graphql resolvers, I'd still query the DB a lot just for the service calls (check the product is active when adding to a cart etc.), so using service level cache just makes more sense.

Keep in mind that it's ESM-only though. I've tested that it can be used (with tsconfig tweaks, #hangout message), you'd probably want to create a small wrapper so your imports aren't all messed up.

Also, I suspect some of the boilerplate could be wrapped to a decorator, similar to what jmcdo did with ogma logger.