Are nested GraphQL queries with specific id values possible? - graphql

I've successfully built a GraphQL API which allows nested queries. Using the generic examples of Countries & States, I can perform a query like this:
query{
country(id:"Q291bnRyeTo0Nw==") {
states {
edges {
node {
id,
name,
area,
population
}
}
}
}
}
What I've discovered I can't seem to do is this:
query{
country(id:"Q291bnRyeTo0Nw==") {
state(id:"U3RhdGU6MzM=") {
edges {
node {
id,
name,
area,
population
}
}
}
}
}
Might there be a way with GraphQL to specify a specific parent and specific child in one query?
Robert
Update: For Daniel's benefit, here is my current GraphQL Query code:
from .models import Country as CountryModel
from .models import State as StateModel
class Query(graphene.AbstractType):
country = graphene.Field(Country, id=graphene.String())
countries = graphene.List(Country)
state = graphene.Field(State, id=graphene.String())
states = graphene.List(State)
def resolve_country(self, args, context, info):
id = args.get('id')
if id is not None:
return CountryModel.objects.get(id=Schema.decode(id))
return None
def resolve_countries(self, args, context, info):
return CountryModel.objects.all()
def resolve_state(self, args, context, info):
id = args.get('id')
if id is not None:
return StateModel.objects.get(id=Schema.decode(id))
return None
def resolve_states(self, args, context, info):
return StateModel.objects.all()

You'd need to define a resolver for both the country field on the Root Query and the state field on the Country type. Here's an example you can copy and paste into Launchpad and see it in action.
The set up for something like Graphene would be a little different, but the idea is the same: the object returned by your country query is made available to the resolver for every field under the state type. You use the id argument passed to the state field to filter the data on that object (in this example, the returned object has a states property) and return the appropriate state.
import { makeExecutableSchema } from 'graphql-tools';
const countries = [
{
id: 1,
name: 'bar',
states: [
{
name: 'foo',
id: 20
}
]
},
{ id: 2 },
];
const typeDefs = `
type Query {
country(id: Int!): Country
}
type Country {
id: Int
state(id: Int!): State
}
type State {
id: Int
name: String
}
`
const resolvers = {
Query: {
country: (obj, args, context) => {
return countries.find(country => country.id === args.id)
},
},
Country: {
state: (obj, args, context) => {
return obj.states.find(state => state.id === args.id)
},
}
}
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
Edit: Assuming the object returned by CountryModel.objects.get(id=Schema.decode(id)) includes a states attribute that is a list of states, you should be able to do something like:
class Country(graphene.ObjectType):
state = graphene.Field(State,
id=graphene.String()
)
# other fields
def resolve_state(self, args, context, info):
id = args.get('id')
if id is not None:
return list(filter(lambda x: x.id == id, self.states)
return None

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

Access return data from resolver in graphql

I want to access the country field from my resolver. The country is being returned by query but since Product is a list I can only access the object inside items return by query. Is there any way I can have access to whole returned data from query or any way to pass it further down as an argument to my resolver function
//schema
type ProductCollectionPage {
items: [Product!]!
}
//resolver
const resolvers = {
Product: {
variants: async (obj: any, args: any, { dataSources }: any): Promise<IProductVariantPage> => {
const { id } = obj;
// want to access country here
return (dataSources.xyz as XyzRepository).retriveProducts(country, id);
}
},
Query: {
products: async (
obj: any,
{ id }: { id: string },
{ dataSources }: any
): Promise<
any
> => {
const locationDetails = await (dataSources.abc as InventoryLocationsRepository).retrieveInventoryLocation(id);
const country = locationDetails.country;
const response = await (dataSources.abc as XyzRepository).retriveProductIds(country);
// response.list === [{id: 1}, {id:2}]
return {
country,
items: response.list
}
}
}
};
As arrays are objects in javascript then you can just assign additional property to response.list:
response.list.country = country;

Get other related records (with id different that queried)

As a newbie to GraphQL I would appreciate some help in the following:
I have query which retrieves its author and that author's books. I would like the author's books to be author's other books, meaning - except the one being queried. What does it involve?
apollo-angular query:
const getBookQuery = gql`
query($id: ID){
book(id: $id){
id
name
year
author {
id
firstName
lastName
books { # <-- give me all _except_ the one with $id
name
year
id
}
}
}
}
`;
and in the schema.js (node.js server) I have something like:
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
book: {
type: BookType,
args: { id: { type: GraphQLID } },
resolve(parent, args) {
const { id } = args;
return Book.findById(id);
},
},
books: {
type: GraphQLList(BookType),
resolve() {
return Book.find({});
},
},
// ... other queries ...
}
})
The solution I am looking for should, obviously, not break other queries for books.
You should be able to achieve the exclusion by adding an argument to the Author type def and then appropriately using that argument in the resolver for books (which should be nested resolver on your Author type). Will need to adapt syntax for apollo-angular.
type Author {
id:
firstName: String
lastName: String
books(exclude: ID): [Book]
}
const resolverMap = {
Query: {
book(arent, args, ctx, info) {
...
}
},
Author: {
books(obj, args, ctx, info) {
// Use args.exclude passed to filter results
},
},
};
const getBookQuery = gql`
query($id: ID){
book(id: $id){
id
name
year
author {
id
firstName
lastName
books(exclude: $id) {
name
year
id
}
}
}
}
`;

Nested query and mutation in type-Graphql

I found a feature in graphql to write nested query and mutation, I tried it but got null. I found the best practices of building graphqL schema on Meetup HolyJs and the speaker told that one of the best ways is building "Namespaced" mutations/queries nested, in this way you can write some middlewares inside the "Namespaced" mutations/queries and for get the Child mutation you should return an empty array because if you return an empty array, Graphql understand it and go one level deep.
Please check the example code.
Example in graphql-tools
const typeDefs = gql`
type Query { ...}
type Post { ... }
type Mutation {
likePost(id: Int!): LikePostPayload
}
type LikePostPayload {
recordId: Int
record: Post
# ✨✨✨ magic – add 'query' field with 'Query' root-type
query: Query!
}
`;
const resolvers = {
Mutation: {
likePost: async (_, { id }, context) => {
const post = await context.DB.Post.find(id);
post.like();
return {
record: post,
recordId: post.id,
query: {}, // ✨✨✨ magic - just return empty Object
};
},
}
};
This is my Code
types
import { ObjectType, Field } from "type-graphql";
import { MeTypes } from "../User/Me/Me.types";
#ObjectType()
export class MeNameSpaceTypes {
#Field()
hello: string;
#Field({ nullable: true })
meCheck: MeTypes;
}
import { Resolver, Query } from "type-graphql";
import { MeNameSpaceTypes } from "./MeNamespace.types";
#Resolver()
export class MeResolver {
#Query(() => MeNameSpaceTypes)
async Me() {
const response = {
hello: "world",
meCheck:{}
};
return response;
}
}
Result of code
query {
Me{
hello
meCheck{
meHello
}
}
}
--RESULT--
{
"data": {
"Me": {
"hello": "world",
"meCheck": {
"meHello": null
}
}
}
}
I got a null instead a meHello resolver. Where am I wrong?
Namespaced mutations are against GraphQL spec as they are not guarranted to run sequentially - more info in this discussion in GitHub issue related to your problem:
https://github.com/MichalLytek/type-graphql/issues/64

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.

Resources