express-graphql resolver args is empty in resolver but info variableValues populated with name and value - graphql

Using apollo-server-express and graphql-tools, I am attempting to create a minimally viable schema from a JSON object:
const books = [
{
"title": "Harry Potter",
"author": 'J.K. Rowling',
"slug": "harry_potter",
},
{
"title": 'Jurassic Park',
"author": 'Michael Crichton',
"slug": "jurassic_park",
},
];
// The GraphQL schema in string form
const typeDefs = `
type Query {
books: [Book]
book(title: String!): Book
}
type Book { title: String!, author: String!, slug: String! }
`;
// The resolvers
const resolvers = {
Query: {
books: () => books,
book: (_, { title }) => books.filter(book => {
return new Promise((resolve, reject) => {
if(book.title == title) {
console.log('hack log resolve book _: ', JSON.stringify(book))
resolve(JSON.stringify(book));
}
})
}),
},
Book: {
title: (root, args, context, info) => {
//args is empty, need to match arg w book.title
/*
context: {
_extensionStack:
GraphQLExtensionStack {
extensions: [ [FormatErrorExtension], [CacheControlExtension] ]
}
}
, root,
*/
console.log('resolve Book args: ', args, 'info', info);//JSON.stringify(root.book))
return books.filter(book => {
if(book.title == root.title) {
return book;
}
});//JSON.stringify({"title": root.title});
}
}
};
// book: (_, { title }) => books.filter(book => book.title == title),
// Put together a schema
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
This is my repository.
When logging and stepping through node_modules/graphql/execution/execute.js, the first param of execute argsOrSchema.variableValues contains the query argument key and value, however the 5th argument variableValues is undefined.
According to some threads such as this GitHub issue I can pull the variableValues from the info argument of my resolver, however I would still like to know why the args object is empty?
Here is a gist of the info log given by GraphQL in the resolver function

The args parameter is populated by the arguments passed to the field being resolved -- any arguments passed to other fields will not be included in the args parameter.
Your schema includes a single argument (title) on the book field of your Query type. That means the resolver for that field will receive the title argument as part of its args parameter, but only if that argument is actually included in your query:
// Request
query {
book(title: "Something") {
title
}
}
// Resolvers
const resolvers = {
Query: {
book: (root, args) => {
console.log(args) // {title: 'Something'}
}
},
}
As opposed to:
// Request
query {
book {
title
}
}
// Resolvers
const resolvers = {
Query: {
book: (root, args) => {
console.log(args) // {}
}
},
}
If you pass in a value for the title argument, the only way to get that value in resolvers for other fields is to parse the info parameter. You would not look at the variableValues property, though because the value passed to an argument could be a literal value or a variable. You'd need to traverse the fieldNodes array and locate the appropriate argument value instead.
However, there's typically no need to go through all that.
If the book field is supposed to just a return a book object, your logic for selecting the right book from the books array should be included in that field's resolver:
const resolvers = {
Query: {
book: (root, args) => {
return books.find(book => book.title === args.title)
}
},
}
There is no reason to include a resolver for the title field on the Book type, unless you need that field to resolve to something other than what it will resolve to by default (the title property on the object returned by the parent field's resolver). This would be sufficient to query all books and an individual book by title:
const resolvers = {
Query: {
book: (root, args) => {
return books.find(book => book.title === args.title)
},
books: () => books,
},
}
Check out the official tutorial from Apollo for more examples and a complete explanation of how resolvers work.

Related

Pattern for multiple types from GraphQL Union

I am learning about Interfaces and Unions in GraphQL (using Apollo Server) and am wondering about something. Using documentation examples, https://www.apollographql.com/docs/apollo-server/schema/unions-interfaces/#union-type, how would I return a result which could return authors and books?
My understanding is that you can only return one object type. If a search result contains and array of both books and authors, how is such a result returned? Can things be structured for this case? I have noticed that __resolveType does not work on an array and can only return a single result (it would return the type for all the objects in the array, not each object in array).
GraphQL TypeDef
const { gql } = require('apollo-server');
const typeDefs = gql`
union Result = Book | Author
type Book {
title: String
}
type Author {
name: String
}
type Query {
search: [Result]
}
`;
Resolver
const resolvers = {
Result: {
__resolveType(obj, context, info){
console.log(obj);
if(obj.name){
return 'Author';
}
if(obj.title){
return 'Book';
}
return null;
},
},
Query: {
search: () => { ... }
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
});
The actual GraphQL query may look something like this and consider the search result is both books and authors:
{
search(contains: "") {
... on Book {
title
}
... on Author {
name
}
}
}
When run, __resolveType(obj, context, info){, obj is:
[{ title: 'A' }, { title: 'B' }, { name: 'C' }]
There's only two ways that would happen:
The search field's type is not actually a list (i.e. it's Result instead of [Result] as shown in the code above.
Your resolver for the search field is returning an array of an array of objects: return [[{ title: 'A' }, { title: 'B' }, { name: 'C' }]]

Is there a way to reconstruct a query from info of a graphql.js resolver?

let root = {
books: (parent, args, info) { ... }
}
let res = await graphql(schema, `{ books { title } }`, root);
Is there already a helper function in graphql.js to reconstruct the query using the info?
yes there is a way:
When you use graphqlHTTP from express-graphql, the 3rd argument is graphQLParams, you can give it the context for instance.
graphqlHTTP(async (request, response, graphQLParams) => ({
schema,
graphiql: true,
context: graphQLParams
}))
Then in your schema definition, you can grab the context from any resolve function like this:
products: {
type: new GraphQLList(productType),
resolve: (parent, args, { query }, info) => {
return getProducts({
query
});
}
},
context.query is your actual query in this example its:
{ products { id } }

How to solve "mutation returning data null"?

i'm using apollo-server and want to learn graphql schema, queries and mutations but iam not getting correct resources for understing how mutation works and how to define mutation in resolvers
i have tried something like adding "mutation" similar to "query" in the resolvers but no use.
#schema
const typeDefs = gql`
type Book {
title: String
author: String
}
type Mutation {
addBook(title: String, author: String): Book
}
type Query {
getBooks: [Book]
}
`;
#resolvers
const resolvers = {
Query: {
getBooks: () => books
}
};
#querying in graphql playground
mutation{
addBook( title: "a sad love story",author:"pavan kalyan"){
title
author
}
}
#result i got
{
"data": {
"addBook": null
}
}
i want to get the title and author in the result same as the arguments iam passing in the query
and no error messages
You need to define the mutation in your resolvers:
const resolvers = {
Query: {
getBooks: () => books,
},
Mutation: {
addBook: (_, {input}) => {
// ... code to add book somewhere
const addedBook = insert(input.title, input.author);
return addedBook; // i.e. {title: input.title, author: input.author};
}
}
}

Building GraphQL Resolver to Return List of Strings -- Receiving [object Object] Instead of Strings

I am developing a web application that queries an OrientDB Graph Database using GraphQL. It uses Apollo Server to resolve incoming GraphQL queries.
I want to build a query that will simply return the 'name' field for each "Topic" Object as a list of Strings. e.g.:
{
"data": {
"allTopicNames": [
"Topic 1",
"Topic 2",
"Topic 3",
"Topic 4"
]
}
}
To do so, I created a Type Definition:
// Imports: GraphQL
import { gql } from 'apollo-server-express';
// GraphQL: TypeDefs
const TYPEDEFS = gql`
type Query {
allTopics: [Topic]
topic(name: String): [Topic]
allTopicNames: [String] //This is the new Type Definition -- we want a list of Strings
}
type Topic {
name: String
}
`;
// Exports
export default TYPEDEFS;
And the associated Resolver:
//Connect to OrientDB
var OrientJs = require('orientjs');
var server = OrientJs({
host: "localhost",
port: "2424",
username: "root",
password: "root"
});
var db = server.use({
name: 'database',
username: 'root',
password: 'root'
});
// GraphQL: Resolvers
const RESOLVERS = {
Query: {
allTopics: () => {
return db.query('SELECT FROM Topic ORDER BY name');
},
allTopicNames: () => {
return db.query('SELECT name FROM Topic ORDER BY name'); //This is the new resolver
},
topic: (obj, args) => {
return db.query('SELECT FROM Topic WHERE name=\'' + args.name + '\' LIMIT 1');
}
}
};
// Exports
export default RESOLVERS;
However, when I try to implement the above Type Definition and Resolver, I receive a list of strings which are all "[object Object]" instead of the actual strings:
{
"data": {
"allTopicNames": [
"[object Object]",
"[object Object]",
"[object Object]",
"[object Object]"
]
}
}
I tried to add some code to the resolver that would iterate through each object and create a proper list of Strings to return:
// GraphQL: Resolvers
const RESOLVERS = {
Query: {
allTopics: () => {
return db.query('SELECT FROM Topic ORDER BY name');
},
allTopicNames: () => {
let the_list_of_records = db.query('SELECT name FROM Topic ORDER BY name').then(res => {
let the_list_of_names = []; //We'll return a List of Strings using this
for(var i = 0; i < res.length; i++){
the_list_of_names.push(res[i]['name']);
}
console.log(the_list_of_names);
return the_list_of_names;
});
},
topic: (obj, args) => {
return db.query('SELECT FROM Topic WHERE name=\'' + args.name + '\' LIMIT 1');
}
}
};
But this didn't work, resulting in a null value being returned instead:
{
"data": {
"allTopicNames": null
}
}
I'm frankly confused as to why I can't get a simple list of Strings to populate via this resolver. Perhaps I'm missing something obvious -- any insight is greatly appreciated!
Your initial approach didn't work as expected because you were returning an array of objects. Your second attempt returns null because you don't return anything inside your resolver. Your resolver should always return a value or a Promise that will resolve to that value, otherwise the resolved value for the field will always be null.
The value of the_list_of_records will be a Promise, so you can just return that and that should be sufficient. But we can make this code a little easier to read using map like this:
allTopicNames: () => {
return db.query('SELECT name FROM Topic ORDER BY name').then(res => {
return res.map(topic => topic.name)
})
}
// using async/await
allTopicNames: async () => {
await topics = await db.query('SELECT name FROM Topic ORDER BY name')
return topics.map(topic => topic.name)
}

Why is my graphql nested query returning null?

I'm new to graphql running into issue with nested queries and need help passing an id to identify a relationship.
Queries
Above you can see the PERFORMED_FOR_Affiliation is null, though it is defined in the schema as an Affiliation type.
type Query {
affiliations(affn_id: ID!): [Affiliation]
performances(pfrm_id: ID!): [Performance]
PERFORMED_FOR_Affiliation(affn_id: ID!): Affiliation
Performance_PERFORMED_FOR(pfrm_id: ID!): [Performance]
}
PERFORMED_FOR_Affiliation query is similar to affiliations query only the relationship should return only 1 affiliation (with a matching uid).
I assume affn_id is not being passed down correctly and not sure how to do that properly. Does the PERFORMED_FOR_Affiliation need its own schema?
Schema
type Performance {
pfrm_id: ID!
mark: Int
affn_id: ID!
PERFORMED_FOR_Affiliation: Affiliation
}
type Affiliation {
affn_id: ID!
name: String
Performance_PERFORMED_FOR: [Performance]
}
I've seen some schemas that use 'nodes' and 'edge' types. As I have many other relationships would that be a better way to define the graph?
Resolvers
import performances from './mockData/performances.js';
import affiliations from './mockData/affiliations.js';
export const resolvers = {
Query: {
affiliations: (root, args) => {
return affiliations;
},
performances: (root, args) => {
return performances;
},
PERFORMED_FOR_Affiliation: (root, args) => {
return affiliations;
},
Performance_PERFORMED_FOR: (root, args) => {
return performances;
},
},
};
MockData
//affiliations.js
module.exports = [
{
"affn_id": "43700F3BE17145399924AC176EACBEF4",
"name": "Richmond Senior"
},
{
"affn_id": "8BDE709AC757416082950B1BEED0CE0A",
"name": "Cedar City"
},
{
"affn_id": "123D201BB17545E3B6ECCCCB5FC61FA3",
"name": "Delta"
}
]
and
// performances.js
module.exports = [
{
pfrm_id: "6BD41C6B1C4B43D199DE42A4A408DF1A",
mark: 1270000,
affn_id: "43700F3BE17145399924AC176EACBEF4",
},
{
pfrm_id: "EA2FBC6AB891460EA557F5B60984AD8A",
mark: 1422400,
affn_id: "8BDE709AC757416082950B1BEED0CE0A",
},
{
pfrm_id: "54A6EEB9552C49AC9F7A87E68AC272A2",
mark: 1422400,
affn_id: "123D201BB17545E3B6ECCCCB5FC61FA3",
},
]
Yes you should implement that in your resolver. I guess you will have a database in the background, e.g. mongoose:
You query for the performances and therein you populate the affiliations
performances: (root, args) => {
return new Promise((resolve, reject) => {
performanceModel.find() // find all performances
.populate("PERFORMED_FOR_Affiliation") // add values for affiliates
.exec((error, performances) => {
if (!error) {
resolve(performances); // resolve correct object
} else {
reject(error);
}
});
});
},

Resources