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

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 } }

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' }]]

How to access data loaded with gatsby-source-graphql in a resolver?

In Gatsby how can I create a resolver that uses data loaded by the gatsby-source-graphql plugin. I'm having trouble figuring out to query the data inside a resolver. Can this be done? Any advice on what I'm missing would be helpful.
something like this in gatsby-node.js ...
exports.createResolvers = ({ createResolvers }) => {
createResolvers({
Query: {
getStructure: {
type: `Structure`,
async resolve(source, args, context, info) {
// assume gatsby-config.js is configured with gatsby-source-graphql and this node exists
const myGraphQlApiNode = await context.nodeModel.runQuery({
query: {
filter: {
fieldName: { eq: "myGqlApi" }
}
},
type: "GraphQLSource"
});
const someGqlApiData = // query all of type MyGqlApi_SomeTypeFromGqlApi loaded via gatsby-source-graphql
return toStructure(someGqlApiData)
}
}
}
});
};
It may be an unofficial solution
Because gatsby doesn’t provide graphql method in createResolvers but do it on createPages
you can do st like this
in gatsby-node.js
let apiHelperGraphql = null
exports.createPages = async ({ actions, graphql }) => {
// steal it from create Pages
apiHelperGraphql = graphql
}
exports.createResolvers = ({ createResolvers }) => {
// and call it here, do what ever you want
apiHelperGraphql(`same as grapql syntax`)
}

GraphQL - does context propagate to downstream resolvers?

If you pass a modified context to a GraphQL resolver does this propagate to all downstream resolvers? Is this specified in the GraphQL specification or implementation specific?
To clarify with an example say I have a query like the following
{
companies {
employees {
positions {
title
}
}
}
}
let's say I start with contextA coming into the companies query and then I have my CompanyResolvers where I get a superSpecialContext and pass this on to the employees dataloader
export const CompanyResolvers = {
employees: async ({ id }: CompanyDTO, args: object, contextA: Context) => {
const superSpecialContext = await getSuperSpecialContext();
return context.dataLoaders.employees.load({ id: company.id, context: superSpecialContext });
}
};
when I get to the positions resolver am I now working with the superSpecialContext or the original contextA (I would actually prefer this to be the case)?
export const EmployeeResolvers = {
positions: async ({ id }: EmployeeDTO, args: object, context: Context) => {
// is my context here contextA or superSpecialContext?
}
};
If you pass a modified context to a GraphQL resolver does this propagate to all downstream resolvers.
Yes, each request gets its own context object for the duration of the request. It gets created in the context function on the GraphQL server.
import { ApolloServer, gql } from 'apollo-server'
import { ExpressContext } from 'apollo-server-express/dist/ApolloServer';
const typeDefs = gql`
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
`;
const books = [
{
title: 'Harry Potter and the Chamber of Secrets',
author: 'J.K. Rowling',
},
{
title: 'Jurassic Park',
author: 'Michael Crichton',
},
];
const resolvers = {
Query: {
books: (obj: any, args: any, context: any) => {
console.log(context.name); // Khalil Stemmler
context.name = 'Billy Bob Thorton'
return books;
},
},
Book: {
title: (obj: any, args: any, context: any) => {
console.log(context.name); // Billy Bob Thorton.
// Should print "Billy Bob Thorton twice", once for each book.
return obj.title
},
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: (expressContext: ExpressContext) => {
// The Apollo implementation of context allows you hook into the
// Express request to get access to headers, tokens, etc- in order
// to grab an authenticated user's session data and put it on context.
const { connection, res, req } = expressContext;
// A new context object is created for every request. This function
// should return an object.
return {
name: 'Khalil Stemmler'
}
}
});
// The `listen` method launches a web server.
server.listen().then(({ url }: { url: string }) => {
console.log(`πŸš€ Server ready at ${url}`);
});
Running the following query:
{
books {
title
author
}
}
We get:
πŸš€ Server ready at http://localhost:4000/
Khalil Stemmler
Billy Bob Thorton
Billy Bob Thorton
Reference: "Context Argument - Apollo Docs".

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

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.

Apollo GraphQL: Calling a Query Twice with apolloClient.query?

I have the following query:
const GET_MY_USERINFOFORIMS_QUERY = gql`
query($userID: String!){
myUserDataForIMs(userID:userID){
name_first
name_last
picture_medium
}
} `;
const withUserInfoForIMs = graphql(GET_MY_USERINFOFORIMS_QUERY, {
options({ userID }) {
return {
variables: { userID: `${userID}`}
};
}
,
props({ data: { loading, myUserDataForIMs } }) {
return { loading, myUserDataForIMs };
},
name: 'GET_MY_USERINFOFORIMS_QUERY',
});
From the Apollo docs, it looks like I may be able to call this query twice from inside the component, using apolloClient.query, doing something like this:
client.query({ query: query1 })
client.query({ query: query2 })
Is there a way to call the query twice, passing a different userID each time?
Found it. :)
const localThis = this;
this.props.ApolloClientWithSubscribeEnabled.query({
query: GET_MY_USERINFOFORIMS_QUERY,
variables: {userID: fromID},
}).then((result) => {
localThis.setState({ fromAvatar: result.data.myUserDataForIMs[0].picture_thumbnail });
});
this.props.ApolloClientWithSubscribeEnabled.query({
query: GET_MY_USERINFOFORIMS_QUERY,
variables: {userID: toID},
}).then((result) => {
localThis.setState({ toAvatar: result.data.myUserDataForIMs[0].picture_thumbnail });
});
If there's a better/more efficient way, please post it.
You can do this by passing Apollo's refetch() method into your component's props alongside the data:
const withUserInfoForIMs = graphql(GET_MY_USERINFOFORIMS_QUERY, {
options({ userID }) {
return {
variables: { userID: `${userID}`}
};
},
props({ data: { refetch, loading, myUserDataForIMs } }) {
return { refetch, loading, myUserDataForIMs };
},
name: 'GET_MY_USERINFOFORIMS_QUERY',
});
...then somewhere in your component, you can refetch the data "manually":
theUserWasChangedSomehow(userID) {
this.props.refetch({ userID });
}

Resources