Closed JoshCheek closed 1 year ago
You can change your method signature like this:
func (r *QueryResolver) Course(ctx context.Context, args *struct{ Name string }) *CourseResolver {
usr := user.FromContext(ctx)
return &CourseResolver{user: usr, requestedCourse: args.Name}
}
Let me give some more info about this. The QueryResolver
in your case is shared between all requests. There is only one instance of QueryResolver
in the entire app. This means that if I allow context in that method I might allow people to shoot themselves in the foot and think that this is a separate instance of that struct. In your example the user
filed would be shared between all requests and you might get undesired behavior. If you need to access the user information only once, I would suggest:
query {
user(id: "<user_id_here>") {
courses {
id
name
}
}
}
This will return only the courses the user is enrolled in.
The
QueryResolver
in your case is shared between all requests. There is only one instance of QueryResolver in the entire app.
Not sure if that happens within the lib, but when I instantiate it, I do schema := gql.MustParseSchema(s, &RootResolver{}, opts...)
. So clearly the RootResolver
is shared between all requests, but I was expecting that the QueryResolver
here was resolved from the RootResolver
on every request.
Perhaps I'm overlooking something, but it doesn't seem like I have a place to setup initial state.
🤔 or like a way to take context from the request and set up initial state from that, and share state between the fields.
eg, say there was a query like:
query {
c1: course(name: "course1") { name enrolled }
c2: course(name: "course2") { name enrolled }
c3: course(name: "course3") { name enrolled }
}
Or something similar to that where I've got different fields doing the same query, leading to something roughly equivalent to an N+1 problem. If they all depend on knowing what courses the user is enrolled in, then I can do that query once and share it between them. But I don't see any way to share that query result.
If you need to access the user information only once, I would suggest:
query { user(id: "<user_id_here>") { courses { id name } } }
I'm not super familiar with GraphQL, are you saying that the right way to do it would be for the frontend app to make one query for all courses, and then another query for all the user's courses, and then when displaying all the courses, check each one against the user's to figure out if the user is enrolled in it?
Also I don't really understand why the user
field here receives an id, wouldn't that allow one user to ask about another user? Or is there a check in the user
resolver to make sure the current user is authorized to view this information about another user? (eg seems like it could lead to privacy issues).
Well, I gave a silly example. The id
argument is not needed indeed in my example. If your schema allows a query like this:
query {
user {
courses {
id
name
}
}
}
your front-end will still send one query and get one response back. The backend will resolve the current user in the user
field resolver and then will pass that user to all the subfield resolvers. So I would change the example you gave to something like this:
query {
user {
courses {
...course
}
}
c1: course(name: "course1") { ...course }
c2: course(name: "course2") { ...course }
c3: course(name: "course3") { ...course }
}
fragment course on Course {
id
name
}
The above would return all the courses the current user is enrolled in. If in addition to that you want to select more courses you can add them to the same query.
@JoshCheek you can also use the data loader to load the user and their courses only once https://github.com/graph-gophers/dataloader
Here are some examples of the dataloader in action https://github.com/graph-gophers/dataloader/tree/v7/example
Hi, there may or may not be a current user saved in the context, I wanted to only need to grab it out of there in the first resolver so that subsequent resolvers didn't need to deal with the context. However, I can't do that, b/c I can't receive the context, I get this error.
It seems like I should be allowed to receive the context there so I can initialize state, since there doesn't seem to be any other way to initialize it for a given query (AFAICT, setting the user into the context is the only way to pass it to the resolvers, but the resolver that instantiates them can't access the context!)
My
go.mod
file contains this entry for graphql-go:github.com/graph-gophers/graphql-go v1.5.1-0.20230307005129-224841f523b3
Here is an example that illustrates approximately what I'm trying to do:
This is the output: