How to create a nested resolver in apollo graphql server
Asked Answered
I

3

25

Given the following apollo server graphql schema I wanted to break these down into separate modules so I don't want the author query under the root Query schema.. and want it separated. So i added another layer called authorQueries before adding it to the Root Query

type Author {
    id: Int,
    firstName: String,
    lastName: String
}  
type authorQueries {
    author(firstName: String, lastName: String): Author
}

type Query {
    authorQueries: authorQueries
}

schema {
    query: Query
}

I tried the following.. you can see that authorQueries was added as another layer before the author function is specified.

Query: {
    authorQueries :{
        author (root, args) {
            return {}
       }
    }
}

When querying in Graphiql, I also added that extra layer..

{
    authorQueries {
        author(firstName: "Stephen") {
            id
        }
    }
}

I get the following error.

"message": "Resolve function for \"Query.authorQueries\" returned undefined",

Isidraisidro answered 1/12, 2016 at 2:52 Comment(2)
Related Apollo docs: apollographql.com/docs/apollo-server/data/resolvers/…Corettacorette
Just wondering If It's good for designing gql like this. authorQueires, productQueries...etc or by authorization meaning. I think It's not bad, but less seen.Kramer
A
30

To create a "nested" resolver, simply define the resolver on the return type of the parent field. In this case, your authorQueries field returns the type authorQueries, so you can put your resolver there:

{
  Query: { authorQueries: () => ({}) },
  authorQueries: {
    author(root, args) {
      return "Hello, world!";
    }
  }
}

So in the technical sense, there is no such thing as a nested resolver - every object type has a flat list of fields, and those fields have return types. The nesting of the GraphQL query is what makes the result nested.

Aiken answered 1/12, 2016 at 16:48 Comment(6)
Is there not an better way, like defining a resolver for the author type, which uses some values of the overgiven root parameter? When i do it like this i have make a resolver for everytime a auther is used in an other typeHibbs
Yep, you need to define a resolver for every field that returns an Author, at least in current GraphQL implementations. Could be better in the future.Aiken
I like your angle about GraphQL not having "nested" resolvers. I had struggled through quite a lot of documentations until I realized that..Ebullition
This does not work for depth > 1. Resolvers can only be defined for properties on the type returned from the query, they cannot be defined for properties on these properties. Unless i'm missing something, this is a major performance issue as you end up performing computations that aren't necessary on every request.Chindwin
I have tested authorQueries: () => ({}) returning the empty object actually worked. If TypeScript complains, you can simply do the casting: authorQueries: () => ({} as authorQueries)Cachou
This doesn't work if authorQueries isn't nullable. And it would be very reasonable for authorQueries to be non-nullable - why would we ever return null for authorQuery?Mariko
C
5

Query.libraries() > Library.books() > Book.author() > Author.name()

Apollo Official related docs (Great example inside):

Resolver chains

https://www.apollographql.com/docs/apollo-server/data/resolvers/#resolver-chains

/* code from:
https://www.apollographql.com/docs/apollo-server/data/resolvers/#resolver-chains
*/

const { ApolloServer, gql } = require('apollo-server');

const libraries = [
  {
    branch: 'downtown'
  },
  {
    branch: 'riverside'
  },
];

// The branch field of a book indicates which library has it in stock
const books = [
  {
    title: 'The Awakening',
    author: 'Kate Chopin',
    branch: 'riverside'
  },
  {
    title: 'City of Glass',
    author: 'Paul Auster',
    branch: 'downtown'
  },
];

// Schema definition
const typeDefs = gql`

# A library has a branch and books
  type Library {
    branch: String!
    books: [Book!]
  }

  # A book has a title and author
  type Book {
    title: String!
    author: Author!
  }

  # An author has a name
  type Author {
    name: String!
  }

  # Queries can fetch a list of libraries
  type Query {
    libraries: [Library]
  }
`;

// Resolver map
const resolvers = {
  Query: {
    libraries() {

      // Return our hardcoded array of libraries
      return libraries;
    }
  },
  Library: {
    books(parent) {

      // Filter the hardcoded array of books to only include
      // books that are located at the correct branch
      return books.filter(book => book.branch === parent.branch);
    }
  },
  Book: {

    // The parent resolver (Library.books) returns an object with the
    // author's name in the "author" field. Return a JSON object containing
    // the name, because this field expects an object.
    author(parent) {
      return {
        name: parent.author
      };
    }
  }

  // Because Book.author returns an object with a "name" field,
  // Apollo Server's default resolver for Author.name will work.
  // We don't need to define one.
};

// Pass schema definition and resolvers to the
// ApolloServer constructor
const server = new ApolloServer({ typeDefs, resolvers });

// Launch the server
server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`);
});
Corettacorette answered 11/10, 2020 at 15:46 Comment(0)
T
1

I found that returning functions on the parent fields return type results in the this arg being bound, and breaks the resolver interface b/c the nested resolver doesn't the parent as the first argument.

For inline type definitions

import {
  graphql,
} from 'graphql';

import {
  makeExecutableSchema, IResolverObject
} from 'graphql-tools';

const types = `
type Query {
  person: User
}

type User {
  id: ID
  name: String,
  dog(showCollar: Boolean): Dog
}

type Dog {
  name: String
}
`;

const User: IResolverObject = {
  dog(obj, args, ctx) {
    console.log('Dog Arg 1', obj);
    return {
      name: 'doggy'
    };
  }
};

const resolvers = {
  User,
  Query: {
    person(obj) {
      console.log('Person Arg 1', obj);
      return {
        id: 'foo',
        name: 'bar',
      };
    }
  }
};

const schema = makeExecutableSchema({
  typeDefs: [types],
  resolvers
});

const query = `{ 
  person {
    name,
    dog(showCollar: true) {
      name
    }
  }
 }`;


graphql(schema, query).then(result => {
  console.log(JSON.stringify(result, null, 2));
});

// Person Arg 1 undefined
// Dog Arg 1 { id: 'foo', name: 'bar' }
// {
//   "data": {
//     "person": {
//       "name": "bar",
//       "dog": {
//         "name": "doggy"
//       }
//     }
//   }
// }

You can also use addResolveFunctionsToSchema as seen in the below gist.

https://gist.github.com/blugavere/4060f4bf2f3d5b741c639977821a254f

Tittletattle answered 23/4, 2018 at 1:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.