Skip to content

GraphQL resolution context #354

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

Closed
calebmer opened this issue Apr 11, 2016 · 13 comments
Closed

GraphQL resolution context #354

calebmer opened this issue Apr 11, 2016 · 13 comments

Comments

@calebmer
Copy link
Contributor

Does this library have a way to store context when resolving a GraphQL query?

For example, I'd like to keep a database connection variable in a context value for per request optimizations.

Ideally the context would be dependent on nesting so if I had one field to authenticate a user (asUser(token: …) { … } for example) I could put the token in the context and nested resolves would recieve it.

I know of the source parameter for resolves but is there a way to take this a step further?

@helfer
Copy link
Contributor

helfer commented Apr 11, 2016

@calebmer: Yes, what you describes works. I wrote a short blog post explaining it, in case you want to read that. Note that that was written when there was no context argument yet, only a root value, but the same principle applies.

@calebmer
Copy link
Contributor Author

Is there a way to parallelize contexts? Maybe with ImmutableJS?

@helfer
Copy link
Contributor

helfer commented Apr 11, 2016

What do you mean by parallelize?

@calebmer
Copy link
Contributor Author

If there are two parallel requests to the same field and context gets changed it will only propagate down to child resolve functions.

For the token use case for instance.

@helfer
Copy link
Contributor

helfer commented Apr 11, 2016

If the same field is requested, won't the same resolver run? If you're worried about doing extra work, that resolver could check the context to see if it's already there before doing anything. You don't have to worry about race conditions unless you yield somewhere in the middle, because node is single-threaded.

@calebmer
Copy link
Contributor Author

Here let me illustrate the scenario I'm think of.

Schema:

type Root {
  posts: [Posts],
  authenticate(token: !String): Root
}

Where authenticate() switches nested queries to an authenticated query. This means private info (if provided the right token) would be visible in queries nested in authenticate().

Parallel or nested authenticate()s would be possible (while uncommon).

{
  posts {
    headline
  }
  userAData: authenticate(token: "a") {
    posts {
      headline
    }
  }
  userBData: authenticate(token: "b") {
    posts {
      headline
    }
    userCData: authenticate(token: "c") {
      posts {
        headline
      }
    }
  }
}

All nested queries (and things may get deeply nested) should use the right token.

@helfer
Copy link
Contributor

helfer commented Apr 12, 2016

I'm not sure I understand why you need the nested authenticate fields, but for the kind of scenario where there are several tokens in the same query you have to pass that token via the return values of resolve functions, because the context is global. Passing the token via return values is a bit messy though, because every resolve function has to pass it on.

However, I don't think you need any of that for the query you wrote above. The way I would write the schema to support such a query, is to create a user type and make sure that the posts field on User only returns that user's post. Then you can do: user(token: "a!) { posts { headline } }, which I think is equivalent to what you're doing above.

@calebmer
Copy link
Contributor Author

A feature that allows for not global state but rather local state to make what would normally be just messily passing around a value a bit neater is the impetus for this issue. Authentication is just the use case where I found this feature could help.

I know Babel maintains some state when transforming ASTs (whether that is truly local I'm not sure), but a feature like that for graphql-js could be helpful.

@helfer
Copy link
Contributor

helfer commented Apr 12, 2016

My question is this: when do you ever have to authenticate more than one
user in a query? What you described above is getting data for multiple
users, which is different from authenticating multiple users. There is
still only one user making the request, even if that user has multiple
tokens for different things.
If what you want is to get data relative to the parent, then that
information should be passed down by the resolvers, because anything
else is going to make your code very hard to debug because of side-effects
and all.

On Mon, Apr 11, 2016 at 5:58 PM, Caleb Meredith [email protected]
wrote:

A feature that allows for not global state but rather local state to make
what would normally be just messily passing around a value a bit neater is
the impetus for this issue. Authentication is just the use case where I
found this feature could help.

I know Babel maintains some state when transforming ASTs (whether that is
truly local I'm not sure), but a feature like that for graphql-js could
be helpful.


You are receiving this because you commented.
Reply to this email directly or view it on GitHub
#354 (comment)

@calebmer
Copy link
Contributor Author

It's a good question and I continually reevaluate how I'm going to do authentication in my project. What I'm trying to do is intelligently create a GraphQL schema from an existing data model without human intervention and as such there are a couple guiding points which have thus far led me to this approach.

  1. No React/front end web assumptions. The GraphQL server I'm working on isn't exclusively for a React/Relay app so I can't make any assumptions on that front. The schema model can't be designed for a specific use case but must be general to many use cases.
  2. HTTP independent. I think GraphQL is perfectly capable of being HTTP independent so while I'm unsure of other protocols at the moment to use with GraphQL I want my schema to resolve without context from HTTP. This means no Authorization header.
  3. Any data in my model can be affected by a user logged in state. I can't safely make a few fields require user information, my use case requires I assume every field in my schema has the potential to be effected by a user being present.

Because of my second constraint auth must be done in GraphQL and there is no way (that I'm aware of) to restrict a field to be a singleton for every request. I don't want to code like there is only ever one user when that is not always guaranteed. I agree I can't think of a use case for many authenticated users in a single query, but I don't want to assume when the consequences could be bad (actually one use case could be a service acting on behalf of many users).

I also agree that passing the token down to each resolver is best for the schema I'm currently entertaining, but would it be a valuable feature to allow resolvers to maintain parent context whilst not passing a value down explicitly.

@jnak
Copy link

jnak commented May 4, 2016

from reading this thread, it seems you guys are talking about the same thing.

@calebmer In #326 they describe how a context is local to a query (ie instantiated on each query) which is you what you are asking for, right?

@calebmer
Copy link
Contributor Author

calebmer commented May 5, 2016

No, not particularly. Rather a context that is set in each resolver and available to the children of the resolver. This is helpful for query composition.

@yaacovCR
Copy link
Contributor

From last comment, I think this issue is currently being tracked in #2692 .

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

5 participants