17
votes

I am using apollo-server and apollo-graphql-tools and I have following schema

type TotalVehicleResponse {
  totalCars: Int
  totalTrucks: Int
}

type RootQuery {
  getTotalVehicals(color: String): TotalVehicleResponse
}

schema {
  query: RootQuery
}

and Resolver functions are like this

{
  RootQuery: {
    getTotalVehicals: async (root, args, context) => {
      // args = {color: 'something'}
      return {};
    },
    TotalVehicleResponse: {
      totalCars: async (root, args, conext) => {
        // args is empty({}) here
        .........
        .........
      },
      totalTrucks: async (root, args, conext) => {
        // args is empty({}) here
        .........
        .........
      }
    }
  }
}

My question is that how can I access args which is available in root resolver(getTotalVehicals) in any of the child resolvers?

4

4 Answers

22
votes

args refer strictly to the arguments provided in the query to that field. If you want values to be made available to child resolvers, you can simply return them from the parent resolver.

{
  RootQuery: {
    getTotalVehicles: async (root, args, context) => {
      return { color: args.color };
    },
    TotalVehicleResponse: {
      totalCars: async (root, args, context) => {
        // root contains color here
      },
      totalTrucks: async (root, args, context) => {
        // root contains color here
      }
    }
  }
}
17
votes

If you know you are using variables there is another way, other than the accepted answer, using the fourth argument of the resolver function: info.

This info argument contains a field variableValues amongst other fields. This field doesn't strictly contain the parent's args, but if your operation is executed with variables that are passed to the parent resolver, then you'll have access to them via the info.variableValues from all the relevant resolver functions.

So if your operation is called like this for example:

query GetTotalVehicalsOperation($color: String) {
  getTotalVehicals(color: $color) {
    totalCars
    totalTrucks   
  }
}

... with variables: {color: 'something'}

you'll have access to the variables from the other resolvers:

{
  RootQuery: {
    getTotalVehicles: async (root, args, context, info) => {
      //info.variableValues contains {color: 'something'}          
      return {};
    },
    TotalVehicleResponse: {
      totalCars: async (root, args, context, info) => {
        //same here: info.variableValues contains {color: 'something'}
      },
      totalTrucks: async (root, args, context, info) => {
        //and also here: info.variableValues contains {color: 'something'}
      }
    }
  }
}
9
votes

Add your arguments to the field

(Client Side) change from:

Car(type: $type, materialType: $materialType){
  id
  material
  name
  ...
}

(Client Side) To:

Car(type: $type){
  id
  material(materialType: $materialType) // moved here
  name
  ...
}

Then, access your argument in your server fieldResolver (material field in this case).

Longer version

Try not to pass your argument through root/info, except IDs and arguments that is not from client, anything else use field level argument (unless you have a very good reason not to)

How?

Let's make it really simple: There are only two level of argument:

  1. Root field argument
  2. Field level argument

Let's say you want to query a customizable car (let's limit to only the seat is customizable for now)

[Root] Car(color:white, type:sedan, seat:leather)

Soon you find yourself, you need to customize the color of seat

[Root] Car(color:white, type:sedan, seat:leather, seatColor:black)

then, business grows, now we need to customize the rim as well:

[Root] Car(color:white, type:sedan, seat:leather, seatColor:black, rimShape:star,rimColor:makeitshine)

Coming next dashboard, exhaust, windows and it is never ending. To solve this: make it to field level:

[Root] Car(color:white, type:sedan)
[Field] seat(color: black, texture:fabric)
[Field] rim(shape:star, color: makeitshine)
[Field] window(feature:bulletproof, tint:cantseeme)
......any other thing you want to add

instead of clumping all arguments into one root, now each field is responsible of its own argument and own its resolver.

Why?

There are a few reasons:

  1. Tight Coupling

From @Bruno Ribeiro in the comment section:

it leads to coupling and it's very hard to scale up schemas

From https://principledgraphql.com/agility#4-abstract-demand-oriented-schema

A schema focused on providing a great developer experience to an app developer building a new feature against the existing graph. Aiming for this standard will help prevent the graph from becoming coupled to a service implementation that could change in the future, and help increase the reuse value of each field added to the graph.

  1. Difficult to troubleshoot

One level is still okay, but when the bad habit got out of hand, and someone in your company found a way to pass the argument down few levels deep through roots, if it gone missing, it is difficult to find out where it is gone. Not fun.

  1. Leaking unnecessary information to children

Passing arguments through root also means passing to every other child, desired one or not.

When to apply?

Whenever you find yourself creating a dedicated resolver for that field, pass the argument to the field (not root, and worse: info)

End of the long whine.

######################

This section is to answer host question.

My question is that how can I access args which is available in root resolver(getTotalVehicals) in any of the child resolvers?

(Server side)

type RootQuery {
   getTotalVehicles(color: String): TotalVehicleResponse
}

type TotalVehicleResponse {
   totalCars(color: String): Int // <-- added arguments
   totalTrucks(offset: Int, limit: Int): Int // <-- added arguments
}
    
schema {
   query: RootQuery
}

then, you can access this args in your resolver argument fields:

// In your child resolver
TotalVehicleResponse{

  totalCars(parent, args, ctx){
    const {color} = args // <-- access your client args here
    return ....
  }

  totalTrucks(parent, args, ctx){
     const {offset, limit} = args // <-- your args from client query
     ...do db query
     return ....
   }
}

In your client query

(Client Side)

Don't forget to add your variables in your nested query field as well.

getTotalVehicles(color: $color){
  totalCars(color: $color) <-- add your variable here
  totalTrucks(offset: $offset, limit: $limit) <-- add your variable here
}
-4
votes

To understand more about variables use in GraphQL

Please refer these links ( You can go through these links in less than 5 mins)

https://graphql.org/learn/queries/#operation-name

https://graphql.org/learn/queries/#variables

https://graphql.org/learn/queries/#fragments

https://graphql.org/learn/queries/#using-variables-inside-fragments

You will get more hold on operation names, variables, fragments and use of variables inside fragments.

Have a look at this link: https://www.prisma.io/blog/graphql-server-basics-demystifying-the-info-argument-in-graphql-resolvers-6f26249f613a

It will help you in understanding more about info argument of resolver function.