Consider following code:
Schema:
const userType = new GraphQLObjectType({
name: 'User',
description: 'User type',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'User id',
},
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'User name',
},
edges: new GraphQLObjectType({
name: 'UserEdges',
description: 'User edges',
fields: () => ({
todos: {
type: new GraphQLList(todoType),
description: 'User todos',
args: {
count: {
name: 'count',
type: GraphQLInt,
},
},
resolve: (user, params) => {
console.log('params', params);
return findTodo(params);
},
},
}),
}),
}),
});
const todoType = new GraphQLObjectType({
name: 'Todo',
description: 'Todo type',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'Todo id',
},
text: {
type: new GraphQLNonNull(GraphQLString),
description: 'Todo text',
},
createdAt: {
type: new GraphQLNonNull(GraphQLString),
description: 'Todo creation date',
},
}),
});
Query:
Query {
viewer {
id,
name,
edges {
todos(count: 3) {
id,
${TodoItem.getFragment('todo')}
}
}
}
}
In the query result edges field is null. Is it a bug or I do something wrong?
edges: above points directly to a type rather than to a field definition object which includes "type" and "description" properties.
I'll try to make this a fast fail error so it's easier to catch
@leebyron Yeah, I've forgot to specify it here. I've found the problem.
Can you tell me how can I operate with virtual fields in my schema? In this example I do not have edges field on my User, but I'd like to create one. How do I do that?
I've done now with following example. Please do not think of edges in terms of Relay. In my code I'd want to store all the relations under the edges virtual field and nothing more.
const userEdge = new GraphQLObjectType({
name: 'UserEdge',
fields: () => ({
todos: {
type: new GraphQLList(todoType),
description: 'User todos',
args: {
count: {
name: 'count',
type: GraphQLInt,
},
},
resolve: (user, params, { rootValue }) => {
return findTodo(params);
},
},
}),
});
const userType = new GraphQLObjectType({
name: 'User',
description: 'User type',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'User id',
},
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'User name',
},
edges: {
type: new GraphQLList(userEdge),
resolve: (user, args, info) => {
// Here I'd like to delegate execution to the edge fields
},
},
}),
});
My query:
Query {
viewer {
id,
name,
edges {
todos(count: 3) {
id,
text
}
}
}
}
Expected result:
"viewer": {
"id": 'u-1',
"name": "user",
"edges": {
"todos": [/* array of todos*/]
}
}
What should I write in the resolve?
Just pass what you need in todos as a result of resolve of edges. If you have overridden all children resolves you can use the result of resolve to pass info.
@freiksenet like this?
const userType = new GraphQLObjectType({
name: 'User',
description: 'User type',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'User id',
},
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'User name',
},
edges: {
type: new GraphQLList(userEdge),
resolve: (...args) => {
return { todos: edgeType.fields().todos(...args) }
},
},
}),
});
This is one way, then you actually don't need a resolve in todos field at all.
You should also move count to edges arguments, not the Edge type.
You basically can do this stuff in two ways.
resolve to children.resolve and use it from parent in every of the children's resolve.Thanks to @freiksenet for helping in solving this issue. If anyone cares about the solution it is a follows:
const userEdges = new GraphQLObjectType({
name: 'UserEdges',
fields: () => ({
todos: {
type: new GraphQLList(todoType),
description: 'User todos',
args: {
count: {
name: 'count',
type: GraphQLInt,
},
},
resolve: (user, params, { rootValue }) => {
return findTodo(params);
},
},
}),
});
const userType = new GraphQLObjectType({
name: 'User',
description: 'User type',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'User id',
},
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'User name',
},
edges: {
type: userEdges,
resolve: () => ({})
},
}),
});
@leebyron This case is about adding virtual field into the schema. I think that GraphQL can provide quite better API for it. The idea here is if we provide plain JS object as a field simply delegate execution to its children.
const userType = new GraphQLObjectType({
name: 'User',
description: 'User type',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'User id',
},
name: {
type: new GraphQLNonNull(GraphQLString),
description: 'User name',
},
edges: {
todos: {
type: new GraphQLList(todoType),
description: 'User todos',
args: {
count: {
name: 'count',
type: GraphQLInt,
},
},
resolve: (user, params, { rootValue }) => {
return findTodo(params);
},
},
},
}),
});
I'm sorry but I don't follow any of this. I'm probably tainted by my understanding of what edges refers to from working with Relay.
You have Edges defined as a list of userEdge and Todos defined as a list of todoType? I'm not sure I follow what Edge refers to here, as a User has a list of them, and whatever they are, they each have a list of Todos?
This case is about adding virtual field into the schema. I think that GraphQL can provide quite better API for it. The idea here is if we provide plain JS object as a field simply delegate execution to its children.
I'm sorry, I don't follow this at all. What does "virtual field" refer to in your parlance? Is edges special, or could the property have been anything? How do we distinguish between this nested object any other framings of the API?
If I reframe your example as:
const userType = new GraphQLObjectType({
name: 'User',
description: 'User type',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'User id',
},
edges: defineEdges({
todos: { ... },
}),
}),
});
could you explain what you expect defineEdges to do or how it might be implemented?
@leebyron Sorry for confusing name. I do not use edges in terms of Relay. In my case edges means collection of relations for the type. I just want to store all the relations for the type in one map but I do not want and/or cant add this on database layer.
From your comment I suppose that you rely on the code in the beginning of that issue. But its not reliable anymore. Please check code in the first half of this comment. Here there is no GraphQLList(userEdges) and I suppose you should get an idea.
What is unclear to me is the role of rootValue with those 'computed' fields:
for example, this works fine, as pointed above in this thread
const { graphql, GraphQLObjectType, GraphQLNonNull, GraphQLInt, GraphQLString, GraphQLList, GraphQLSchema } = require('graphql');
const getStuffs = () => Array.from({ length: 50 }, (_, i) => ({ id: `_${i}`, name: `n${i}` }));
const StuffType = new GraphQLObjectType({
name: 'Stuff',
description: 'Some stuff',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLString),
description: 'Stuff id',
},
name: {
type: GraphQLString,
description: 'Stuff name',
},
custom: {
type: GraphQLString,
description: 'cus',
resolve: () => 'ok',
}
})
});
const QueryType = new GraphQLObjectType({
name: 'Query',
description: 'The root of all... queries',
fields: () => ({
stuffs: {
type: new GraphQLList(StuffType),
description: '..',
args: {
first: {
name: 'first',
type: GraphQLInt,
},
},
resolve: (o, { first = 20 } = {}) => getStuffs().slice(0, first)
}
})
});
const schema = new GraphQLSchema({
query: QueryType,
});
const gql = (source, variableValues) => graphql({
schema,
source,
variableValues,
});
(async () => {
const d = await gql(`{
stuffs(first: 10){
id
name
custom
}
}`);
console.log(d.data.stuffs); // returns arrays of `{ id: '_4', name: 'n4', custom: 'ok' }` ,'s
})();
but using rootValue and buildSchema:
const { graphql, buildSchema } = require('graphql');
const str = `
type Query {
rand: [Float]
stuffs(first: Int): [Stuff]
}
type Stuff {
id: ID!
name: String
custom: String
}
`;
const schema = buildSchema(str);
const getStuffs = () => Array.from({ length: 50 }, (_, i) => ({ id: `_${i}`, name: `n${i}` }));
const rootValue = {
stuffs: ({ first = 20 }) => getStuffs().slice(0, first),
Stuff: {
custom: () => 'ok',
}
};
const gql = (source, variableValues) => graphql({
schema,
rootValue,
source,
variableValues,
});
(async () => {
const d = await gql(`{
stuffs(first: 10){
id
name
custom
}
}`);
console.log(d.data.stuffs); // returns arrays of `{ id: '_4', name: 'n4', custom: null }` ,'s
})();
the computed field custom are nulls, I guess Stuff in the rootValue isn't even invoked, and shouldn't even be here.
It'd be possible to manually merge that computed field in the root query adding a .map(s => ({...s, custom: 'ok'})), but it's not practical with more depth levels
It's what makeExecutableSchema in graphql-tools allows: https://launchpad.graphql.com/553zz3zr79
Basically I would prefer the second approach for defining schemas, but still allow computed fields. Or understand why they are not equivalent
(note: I intentionally omitted custom in getStuffs, the goal was to merge it using graphql resolvers)
@caub It's better to use stack overflow for such questions: https://github.com/graphql/graphql-js/blob/master/.github/ISSUE_TEMPLATE.md#questions-regarding-how-to-use-graphql
Is this what makeExecutableSchema in graphql-tools aims to solve?
It uses a different approach to the problem.
the computed field custom are nulls, I guess Stuff in the rootValue isn't even invoked
You return new root value from stuffs and it was missing custom property. Here is a fixed version:
const { graphql, buildSchema } = require('graphql');
const str = `
type Query {
rand: [Float]
stuffs(first: Int): [Stuff]
}
type Stuff {
id: ID!
name: String
custom: String
}
`;
const schema = buildSchema(str);
class Stuff {
constructor(i) {
this.id = `_${i}`;
this.name = `n{i}`;
}
custom() {
return 'ok';
}
}
const getStuffs = () => Array.from({ length: 50 }, (_, i) => new Stuff(i));
const rootValue = {
rand: () => Array.from({ length: 10 }, () => Math.random()),
stuffs: ({ first = 20 }) => getStuffs().slice(0, first),
};
const gql = (source, variableValues) => graphql({
schema,
rootValue,
source,
variableValues,
});
(async () => {
const d = await gql(`{
stuffs(first: 10){
id
name
custom
}
}`);
console.log(d.data.stuffs); // returns arrays of `{ id: '_4', name: 'n4', custom: 'ok' }` ,'s
})();
Most helpful comment
Thanks to @freiksenet for helping in solving this issue. If anyone cares about the solution it is a follows:
@leebyron This case is about adding virtual field into the schema. I think that GraphQL can provide quite better API for it. The idea here is if we provide plain JS object as a field simply delegate execution to its children.