Pattern for multiple types from GraphQL Union - graphql

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

Related

Apollo Client - Cache Redirects for sub-types

I am using Apollo in production for about a year and I am trying to optimize my cache management.
Let's imagine the following fictive simple schema:
type Query {
allBooks: [Book]
allCups: [Cup]
allColors: [Color]
}
type Book {
id: Int
name: String
cover_color_id: Int
CoverColor: Color
}
type Cup {
id: Int
name: String
cover_color_id: Int
CoverColor: Color
}
type Color {
id: Int
name: String
hex_code: String
}
I would like to configure cacheRedirects so that when I demand the Book.CoverColor or Cup.CoverColor (via allBooks for instance ); it will first look for the Color with matching ID in the cache, before asking the server for it.
Is that possible ?
Thanks in advance!
PS: I tried this, which doesn't seem to work:
cacheRedirects: {
Query: {
// Stuff that perfectly works
},
Book: {
// this is not even executed :(
CoverColor: (book, args, { getCacheKey }) => {
return getCacheKey({
__typename: 'Color',
id: book.cover_color_id
})
}
}
}
It depends on what your queries look like.
if your allBooks redirect returns list of ids and your query has returnPartialData enabled it should work.
if you querying allBooks without hitting redirect, there is no reason for apollo to use cached fields on each element of [Book] if it already has all data
Make a query for a single book and it should work as you expect.
type Query {
allBooks: [Book]
allCups: [Cup]
allColors: [Color]
book(id: Int): Book
}
const cacheRedirects: CacheResolverMap = {
Query: {
book: (_, args, { getCacheKey }) =>
getCacheKey({ __typename: 'Book', id: `${args.id}` }),
},
Book: {
CoverColor: (book, args, { getCacheKey }) => {
return getCacheKey({
__typename: 'Color',
id: book.cover_color_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.

Graphql Typecheck on redux like events

I'm implementing a graphql server over some existing REST api using apollo-server.
There is and endpoint returning a list of redux-like events, where Hi have a type and a payload.
type is a string and payload is an object. e.g.
[
{
type:"joined"
from:"member1"
payload:{
received_events:"1518377870416"
invited_by:"member2"
}
},
{
type:"text"
from:"member1"
payload:{
test_string:"hello"
}
}
]
What I need to check is the following:
1) type is an enum joined|text
2) from is a String
3) if type == joined then the payload should contain received_events and invited_by, if type == text then payload should contain test_string
What is the best way to do it? I'm looking at the scalar , and Union, but I'm not sure what to do.
One way to solve this is to inject the type from your event type into the payload object. It can then be used inside the __resolveType resolver for your union. A simple example:
const typeDefs = `
type Query {
events: [Event]
}
type Event {
type: String
payload: Payload
}
union Payload = Text | Joined
type Text {
test_string: String
}
type Joined {
received_events: String
invited_by: String
}
`;
// Since your type names and the specified types are different, we
// need to map them (resolveType needs to return the exact name)
const typesMap = {
text: 'Text',
joined: 'Joined'
}
const resolvers = {
Query: {
events: (root, args, context) => {
return [
{ type: 'text', payload: { test_string: 'Foo' } }
];
},
},
// We can use the resolver for the payload field to inject the type
// from the parent object into the payload object
Event: {
payload: (obj) => Object.assign({ type: obj.type }, obj.payload)
},
// The type can then be referenced inside resolveType
Payload: {
__resolveType: (obj) => typesMap[obj.type]
}
};

I need help understanding Relay OutputFields, getFatQuery

This is the code from official docs of relay, This is for GraphQLAddTodoMutation
const GraphQLAddTodoMutation = mutationWithClientMutationId({
name: 'AddTodo',
inputFields: {
text: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
todoEdge: {
type: GraphQLTodoEdge,
resolve: ({localTodoId}) => {
const todo = getTodo(localTodoId);
return {
cursor: cursorForObjectInConnection(getTodos(), todo),
node: todo,
};
},
},
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
},
mutateAndGetPayload: ({text}) => {
const localTodoId = addTodo(text);
return {localTodoId};
},
});
I think mutateAndGetPayload executes first then outputFields? since it used localTodoId object as parameter, I see localTodoId object returned from mutateAndGetPayload.
and this is the code for relay mutation.please look at the getFatQuery
export default class AddTodoMutation extends Relay.Mutation {
static fragments = {
viewer: () => Relay.QL`
fragment on User {
id,
totalCount,
}
`,
};
getMutation() {
return Relay.QL`mutation{addTodo}`;
}
getFatQuery() {
return Relay.QL`
fragment on AddTodoPayload #relay(pattern: true) {
todoEdge,
viewer {
todos,
totalCount,
},
}
`;
}
getConfigs() {
return [{
type: 'RANGE_ADD',
parentName: 'viewer',
parentID: this.props.viewer.id,
connectionName: 'todos',
edgeName: 'todoEdge',
rangeBehaviors: ({status}) => {
if (status === 'completed') {
return 'ignore';
} else {
return 'append';
}
},
}];
}
getVariables() {
return {
text: this.props.text,
};
}
getOptimisticResponse() {
return {
// FIXME: totalCount gets updated optimistically, but this edge does not
// get added until the server responds
todoEdge: {
node: {
complete: false,
text: this.props.text,
},
},
viewer: {
id: this.props.viewer.id,
totalCount: this.props.viewer.totalCount + 1,
},
};
}
}
I think the todoEdge is from the outputFields from GraphQL? I see a viewer query on it, why does it need to query the viewer? How do I create a getFatQuery? I would really appreciate if someone help me understand this more and about Relay mutation.
mutateAndGetPayload executes then returns the payload to the outputFields
mutationWithClientMutationId
Source-Code
starWarsSchema example
mutationWithClientMutationId
inputFields: defines the input structures for mutation, where the input fields will be wraped with the input values
outputFields: defines the ouptput structure of the fields after the mutation is done which we can view and read
mutateAndGetPayload: this function is the core one to relay mutations, which performs the mutaion logic (such as database operations) and will return the payload to be exposed to output fields of the mutation.
mutateAndGetPayload maps from the input fields to the output fields using the mutation
operation. The first argument it receives is the list of the input parameters, which we can read to perform the mutation action
The object we return from mutateAndGetPayload can be accessed within the output fields
resolve() functions as the first argument.
getFatQuery() is where we represent, using a GraphQL fragment, everything
in our data model that could change as a result of this mutation

Resources