Can a GraphQL resolver force arguments in parent to be retrieved? - graphql

If I have 2 types: User and Note with the following schema:
query {
getUser(userId: ID!): User
}
type User {
userId: ID
email: String
notes: [Note]
}
type Note {
noteId: ID
text: String
}
I am writing a resolver for User#notes. Now say notes need to be retrieved by email address, so I actually need the root object passed to the resolver to contain the email field, is there anyway I can force GraphQL to query the email field in the User object even if the user has not requested it?
In terms of code, from what I see, this is how I can write a resolver. How can I ensure obj.email is requested whenever the user requests the note field?
User: {
notes(obj, args, context, info) {
// How can I ensure obj.email is requested?
return NoteRetriever.getNotesByEmail(obj.email);
}
}
Edit
I am wondering about the case where the parent resolver doesn't resolve the email field unless explicitly requested. What if we need to make an API call to get the email for the user? So by default we don't request it. However, when the notes is requested, it makes sense to request the email too.
Is there a way for the resolver to specify dependency on parent fields - to ensure that gets requested?

The "parent" value passed to your resolver as the first parameter is exactly what was returned in the parent field's resolver (unless a Promise was returned, in which case it will be whatever the Promise resolved to). So if we have a resolver like this:
Query: {
getUser: () => {
return {
userId: 10,
email: 'user#example.com',
foobar: 42,
}
}
}
and a query like:
query {
getUser {
id
notes
}
}
What's passed to our notes resolver is the entire object we returned inside the resolver for getUser.
User: {
notes(obj, args, context, info) {
console.log(obj.userId) // 10
console.log(obj.email) // "user#example.com"
console.log(obj.foobar) // 42
}
}
The parent value will be the same, regardless of the fields requested, unless the parent field resolver's logic actually returns a different value depending on the requested fields. This means you can also pass down any number of other, arbitrary entries (like foobar above) from the parent to each child field.
EDIT:
Fields are resolved independently of one another, so there is no mechanism for declaring dependencies between fields. If the getUser resolver is looking at the requested fields and making certain API calls based on requested fields (and omitting others if those fields were not requested), then you'll need to modify that logic to account for the notes field needing the user email.

I think the expectation is that if you control the query of the parent, and expect the value in the child, you should ensure the required value is always resolved by the parent.
There is, however, a way to do what you are asking when merging schemas. This is described here https://www.apollographql.com/docs/graphql-tools/schema-stitching.
Basically would need to have a base schema that is something like
type Query {
getUser(userId: ID!): User
}
type User {
userId: ID
email: String
}
With the same resolvers as you have now, and a second schema that is something like
type Note {
noteId: ID
text: String
}
extend type User {
notes: [Note]
}
Along with something like
import { mergeSchemas } from 'graphql-tools';
const finalSchema = mergeSchemas({
schemas: [userSchema, noteSchema],
resolvers: {
User {
notes: {
fragment: '... on User { email }',
resolve: notesResolver
}
}
}
});
console.dir(await graphql(finalSchema, `query { ... }`));
Notice the fragment property defining the email field. In the link above it describes how this will force resolution of the given field on the parent when this child is resolved.

Related

Is there any way limit the form of the query in graphql

For example, if there are two types User and Item
type User {
items: [Item!]!
}
type Item {
id: ID!
name: String!
price: Int!
}
If one user has PARTNER role.
I want to prevent it from being called only in the form of the query below.
query Query1 {
user {
items {
name
}
}
}
If user call another query, I want to indicate that user doesn't have permission.
query Query2 {
user {
items {
id
name
}
}
}
In short. if (Query1 != Query2) throw new Error;
Your question is a bit hard to follow but a couple things:
A GraphQL server is stateless - you cannot (and really should not) have a query behave differently based on a previous query. (If there's a mutation in between sure but not two queries back to back)
access management is normally implemented in your resolvers. You can have the resolver for the item id check to see if the user making the query has the right to see that or not and return an error if they don't have access.
Note that it can be bad practice to hide the id of objects from queries as these are used as keys for caching on the client.

Passing variables in GraphQL

I'm trying to run a GraphQL query in the AWS AppSync console:
query MyQuery {
getUserInfoById(id: "1234566789") {
account {
id // need this value for getAvailableCourses
}
}
getAvailableCourses(accountId: "", pageNumber: 0) {
data {
id
name
type
}
}
}
Basically I need the value account.id in getUserInfoById for getAvailableCourses. I'm obviously new to GraphQL. How would I go about this?
To the best of my knowledge, there can be two ways you can do this.
You can handle this in your frontend by getting user's id
from the session info and pass it to the other query.
You can also merge these two queries and make it one. You will also have to change the respective fields. Then attach a resolver with AvailableCourses and use $ctx.source.id in the resolver to get further details. Schema would look something like this
type Account {
id : ID!
availableCourses: AvailableCourses
..
}
type AvailableCourses {
name: String!
type: String!
..
}
type Query {
getUserInfoById(id: ID!): Account
}
Using the returned fields as inputs for a second query into your datasource is precisely what field resolvers are for. I can't say for sure since I don't know your schema or access patterns but it looks like you need to make available courses a sub field of the user.

GraphQL: Use returned value from field as parameter of sub-query [duplicate]

Imagine the following query:
query {
user {
id
}
SomeOtherStuff(id: <--- I want to pass the id obtained from user) {
id
}
}
How do you pass a parameter obtained from one query to another ?
In GraphQL, fields at each "level" of the request are executed and resolved in parallel. In your example, user and SomeOtherStuff are both fields of the same type (the root Query type) -- so they will be resolved at the same time. That means each query essentially is not aware of the other or what the other resolved to.
You would have to handle this kind of scenario client side. In other words, request the user first, parse the response for the id and then make the second request.
Edit: In Apollo, you would utilize compose for this purpose:
const userQuery = gql`query User { user { id } }`;
const stuffQuery = gql`query SomeOtherStuff($id: ID) { someOtherStuff(id: $id){ stuff } }`;
export default compose(
graphql(userQuery, { name: 'userData' })
graphql(stuffQuery, { name: 'stuffData', options: ({userData:{id}={}}) => ({variables: {id}}) }),
)(YourComponent)
I agree with #DanielRearden. You should make type-resolvers so you can go infinitely deep into the graph. I made a simple server example here that shows deep relationships. Because all the noun-fields are references, it goes infinitely deep for any query.
With that server, you can run a query like this, for example:
{
hero {
name
friends {
name
friends {
name
friends {
name
friends: {
name
}
}
}
}
}
}
So, in your example, structure it like this:
query {
user {
id
otherStuff {
id
}
}
}
I was looking for same scenario and landed on this question. You can get it work other way around. It all depends how you have written your graphql resolver and you need to make sure that your database relations are intact. I have got it working like this.

Passing the query param from top level to filed level

I have a graphql query like the following:
userInfo (id: userId) {
name
email
address {
street
country
}
}
So the name and email will be resolved by a rest endpoint called details, however the address fields needs to be resolved by another rest endpoint called address. address rest endpoint also needs the userId passed in userInfo (as my rest service expects that field). I'm not sure, how I can design my resolver in this case?
With same regards, can a field once resolved can send data to another field? Is that possible in grapqhql?
In GraphQL, every field is said to resolve to a particular value. In GraphQL.js, this value is either whatever value was returned by the resolver, or, if the resolver returned a Promise, whatever that Promise resolved to. This value is what is passed down to each child field's resolver as the first parameter.
In practice, this means that even though your type has specific fields and the object you return should match those fields, it can also include any number of additional properties.
For example, your userInfo resolver can something look like this:
async (parent, args, context, info) => {
const { name, email } = await getDetails(args.id)
return {
name
email
id: args.id,
}
}
Even though you don't have an id field, we can still include that in the object we return. This way, we can pass some additional info, like our arguments, down to the resolvers of any children fields. Then, inside your address resolver:
async (parent, args, context, info) => {
// `parent` contains `name`, `email` and `id` properties
return getAddress(parent.id)
}
I would add (to #daniel answer) that you can pass this id explicitely in query (w/o address resolver changes).
userInfo (id: userId) {
name
email
address (userID: userId) {
street
country
}
}
To make it (address resolver) universal we can use both parameter sources:
async (parent, args, context, info) => {
// `parent` (user) can contain `id` property
return getAddress(args.userID ? args.userID : parent.id)
}
because you should have a possibility to query only address (for given userId) as it is possible with rest api.

Pass obtained field to another (nested) query in GraphQL

Imagine the following query:
query {
user {
id
}
SomeOtherStuff(id: <--- I want to pass the id obtained from user) {
id
}
}
How do you pass a parameter obtained from one query to another ?
In GraphQL, fields at each "level" of the request are executed and resolved in parallel. In your example, user and SomeOtherStuff are both fields of the same type (the root Query type) -- so they will be resolved at the same time. That means each query essentially is not aware of the other or what the other resolved to.
You would have to handle this kind of scenario client side. In other words, request the user first, parse the response for the id and then make the second request.
Edit: In Apollo, you would utilize compose for this purpose:
const userQuery = gql`query User { user { id } }`;
const stuffQuery = gql`query SomeOtherStuff($id: ID) { someOtherStuff(id: $id){ stuff } }`;
export default compose(
graphql(userQuery, { name: 'userData' })
graphql(stuffQuery, { name: 'stuffData', options: ({userData:{id}={}}) => ({variables: {id}}) }),
)(YourComponent)
I agree with #DanielRearden. You should make type-resolvers so you can go infinitely deep into the graph. I made a simple server example here that shows deep relationships. Because all the noun-fields are references, it goes infinitely deep for any query.
With that server, you can run a query like this, for example:
{
hero {
name
friends {
name
friends {
name
friends {
name
friends: {
name
}
}
}
}
}
}
So, in your example, structure it like this:
query {
user {
id
otherStuff {
id
}
}
}
I was looking for same scenario and landed on this question. You can get it work other way around. It all depends how you have written your graphql resolver and you need to make sure that your database relations are intact. I have got it working like this.

Resources