Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Resolver Middleware #1516

Closed
dereklavigne18 opened this issue Sep 7, 2018 · 14 comments
Closed

[Feature Request] Resolver Middleware #1516

dereklavigne18 opened this issue Sep 7, 2018 · 14 comments

Comments

@dereklavigne18
Copy link

I'm interested to hear how others feel about the idea of using middleware installed on resolver functions to allow all resolvers in a schema to perform common functionality.

As my schema grows I've found more and more of a need to add some of the same logic to all my resolvers. It would be nice if we could add some middleware that would be executed on resolvers in the schema when we go to execute the query. This could allow users to implement things like custom error handling, logging, and authorization on all resolvers without the need to add them to each resolver on the schema.

@IvanGoncharov
Copy link
Member

@dereklavigne18 That sounds great 👍
I think it has a potential to address a bunch of feature requests like #284, #662, etc.

One important note is that we should be extremely careful not to add a performance penalty, especially for big queries that resolved asynchronously. See this discussion in #723.

@bogdanned
Copy link

+1 to @dereklavigne18. Custom error handling and authorization middlewares would save so much time.

@elevenpassin
Copy link
Contributor

What if it was more event oriented? As in, instead of having a middleware sort of thing, we can have an event emitter api of sorts, and have a couple of options like:

  • BeforeResolverExecution
  • AfterResolverExecution

Later on we can just add more event handlers which can be plugged into.

@stalniy
Copy link

stalniy commented May 8, 2019

I don't think this is a good suggestion. It definitely will make graphql resolvers to be more complicated than they should be.

As an alternative approach, you can define your resolvers as async function composition (or something like in koa pipeline). Each function is a middleware. Very likely all top level resolvers will have the same stages. For example:

// stages for loading logic
[
   'authenticate',
   'load',
   'checkIfFound',
   'authorize',
   'respond'
]

Later you may create a function function which can return different pipelines for different situations. Lets say that when we want to load a list of objects we have loadCollection pipeline factory, so then it can be used like this in resolvers:

const Query = {
  users: loadCollection('User') // User is a model name it can be any model name from your models,
  
  user: loadResource('User'),

  login: loadResource('User', {
     authenticate: false, // lets make login query to be publicly accessible
     load: actions.loadUserByCredentials, // overwrite load stage
  }),
}

const Mutation = {
   createUser: mutateResource('create', 'User', {
       validate: ... // custom validation function
   })
}

You may have different stages (with default implementations, aka middlewares) for different types of Queries and Mutations (or you can customize default implementation by passing an object with stage names)

The good point about factory pipeline is that eventually your code is

  • super easy to test (because you have a bunch of small functions which are composed together)
  • API independent, so you can quickly expose the same functionality via REST or GraphQL or whatever you want

This pattern is very similar to Command pattern from Domain Driven Design. And this is what I used with GraphQL during migration from Express.

@Cito
Copy link
Member

Cito commented Nov 30, 2019

Note that there is already a middleware implementation in GraphQL-core (see middleware.py and test_middleware) where you can steal ideas from.

@fabienjuif
Copy link

fabienjuif commented May 8, 2020

We wrote this naive implementation of middlewares here if you are still interested: https://github.com/unirakun/graphql-directives-middlewares

The solution given by @stalniy seems quite good to me tho!

@sibelius
Copy link

I think we can solve this in userland instead

@danielrearden
Copy link
Contributor

@sibelius The community already has

@sibelius
Copy link

I don’t think this should be in the graphql-js core package

@jgcmarins
Copy link

I've been using an approach like this: https://github.com/JCMais/graphql-yup-middleware

@jgcmarins
Copy link

jgcmarins commented Jun 29, 2020

As @sibelius said and me and @danielrearden pointed, we already have a solution for that out of graphql-js.
Not sure if it makes sense to bring it here. I think that graphql-js exists to implement the graphql-spec.
Any implementation that does not belong to the GraphQL specification, should be implemented out of graphql-js

@vwkd
Copy link

vwkd commented Jun 20, 2023

I'd like to see resolvers becoming full-fledged middleware. It seems currently resolvers are almost like middleware, but limited in that a parent can only run code before but not after the child resolvers. Hence the parent resolver can not modify the child resolver responses and can't handle their errors.

One use case is handling resolver errors in the parent resolver. For example, if a certain error was thrown in a child resolver, the parent resolver could decide to retry the whole child resolver chain again. This is currently not achievable with user-land middleware.

Likely graphql-js would need to change it's validation model, since parent resolvers could now change the return values from child resolvers and handle their errors. It could validate the response of a resolver and throw an GraphQL error if it doesn't validate. The parent resolver could catch and rethrow it, or could handle it and do something. After all middleware is run, graphql-js would validate the whole response again to make sure it's still valid.

@yaacovCR
Copy link
Contributor

yaacovCR commented Nov 4, 2024

Closing this issue as solved in user land. In terms of retrying on error, that functionality seems at first glance counter to the specification — there may be an algorithmic equivalence that makes it neutral, but it would still seem to be best done outside the reference implementation.

@yaacovCR yaacovCR closed this as completed Nov 4, 2024
@Urigo
Copy link
Contributor

Urigo commented Nov 13, 2024

The approach that @the-guild-org is taking and recommending for middleware and plugins for graphql-js is the approach of the Envelop plugin system.
We believe it is a superior approach to the graphql-middleware library (which we've maintained till we've managed to migrate all of our users from it)
We have a whole plugin ecosystem around Custom error handling, Authentication and Authorization, Tracing and Metrics, security, caching and many others, including many custom plugins that companies create for themselves.

It is also the core of our GraphQL Yoga Plugin system and our Hive Gateway Plugin system.
You can read more about the plugin system on these resources:

https://the-guild.dev/graphql/envelop/docs/plugins/lifecycle
https://the-guild.dev/graphql/yoga-server/docs/features/envelop-plugins
https://the-guild.dev/graphql/hive/docs/gateway/other-features/custom-plugins

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests