graphql: sort by nested field - graphql

Let's say I have 2 tables:
- Users (id, name, post)
- Posts (id, message, user)
How can I fetch first 10 Posts order by User's name(desc)?
Here's how my schema looks like:
var PostType = new GraphQLObjectType({
name: "Post",
fields: () => ({
id: { type: GraphQLInt },
message: { type: GraphQLString },
user: {
type: UserType,
args: {
orderBy: { type: sortType }
},
resolve(parent, args) {
console.info("Post resolve called.");
return userMap[parent.user];
}
}
})
});
var RootQuery = new GraphQLObjectType({
name: "RootQueryType",
fields: {
allPosts: {
type: new GraphQLList(PostType),
resolve(parentValue, args) {
console.info("allPosts resolve called.");
return postData;
}
}
}
});
And Query:
{
allPosts {
message
user (orderBy: {field: "name", direction: ASC}) {
name
}
}
}
Is there any way, I can call user resolver function before allPosts resolver function? Because, I am trying to fetch 10 users sorted by name and then pass post ids to allPosts resolver.

GraphQL fields are resolved in a top-down fashion. That means allPosts is resolved first, then then message and user fields (simultaneously) and then the name field. This has to happen, as the "parent" or root field's resolved value determine's the value that's then passed to the resolver for its children fields as the root value. Information flows from "higher" resolvers to "lower" ones, but not the other way around.
Your orderBy argument here probably should be an argument on the allPosts field rather than the user field. There's two reasons to do that: (1) conceptually, regardless of the sort criteria, you are sorting the Posts returned by allPosts -- by convention, it just makes sense to put the sort there; (2) the argument is probably needed by the allPosts resolver more than it's needed by the user resolver.
To make the above work, you'll probably need to modify how you identify the sort criteria (making field a path like user.name for example). You may also need "lift" the logic for populating the users up into the allPosts resolver. For example:
resolve(parentValue, { sortBy: { path, direction } }) {
const data = postData.map(post => {
post.user = userMap[post.user]
return post
});
// using lodash
return orderBy(data, [(post) => get(post, path)], [direction])
}
It is possible to determine the selection set for other fields inside the request, including the arguments, by parsing the info object that's passed in as the fourth parameter to the resolver function. It's a pain though and I don't know if this particular case really justifies doing all that. You can read more about that approach in this answer.

Related

Graphql sending empty string

I am using graphql to get some data.
let { loading, error, data } = useQuery(GET_JOBS_SEARCH, {
variables: {
category: category,
type: jobType
}
});
Now when I update the category the API gets called with variables like
variables: {category: "leadership", type: ""}
Now I don't want the type:'' in my call.

graphqljs resolver with multiple arguments

I am trying to figure out the best way to write a resolver that filters on multiple arguments. I have the following graphql type
const userQuery = new GraphQLObjectType({
name: 'Query',
fields: {
Users: {
type: new GraphQLList(User),
args: {
userId: { type: GraphQLString }
},
resolve: function (_, { UserId}) {
return new Promise((resolve, reject) => {
//Code to query the data store for the user with the given UserId
})
}
}
}
});
The User type has the following fields
Name
UserId
Type
Gender
Now if I want to introduce the ability to filter the user based on the name, then what is the best way to do it. The only way I can think of is to modify the resolver to include the additional args and then based on what is passed in send it to the database. For example
const userQuery = new GraphQLObjectType({
name: 'Query',
fields: {
Users: {
type: new GraphQLList(User),
args: {
userId: { type: GraphQLString }
},
resolve: function (_, { UserId, name}) {
return new Promise((resolve, reject) => {
//Check which argument is passed in and then run the query against the datastore
})
}
}
}
});
Isn't there a better way to do this? If I want the user to be able to filter on another attribute then it gets more complicated, and the resolve function is going to get huge and complicated.

I have confusion on relay and graphql resolve method

Apologies if this is a stupid question. this is the code for relay/graphql pagination that's confusing me:
const GraphQLTodo = new GraphQLObjectType({
name: 'Todo',
fields: {
id: globalIdField('Todo'),
text: {
type: GraphQLString,
resolve: (obj) => obj.text,
},
complete: {
type: GraphQLBoolean,
resolve: (obj) => obj.complete,
},
},
interfaces: [nodeInterface],
});
/* When pagination is needed, make a connection */
const {
connectionType: TodosConnection,
edgeType: GraphQLTodoEdge,
} = connectionDefinitions({
name: 'Todo',
nodeType: GraphQLTodo,
});
const GraphQLUser = new GraphQLObjectType({
name: 'User',
fields: {
id: globalIdField('User'),
todos: {
type: TodosConnection,
args: {
status: {
type: GraphQLString,
defaultValue: 'any',
},
...connectionArgs,
},
resolve: (obj, {status, ...args}) =>
connectionFromArray(getTodos(status), args),
},
totalCount: {
type: GraphQLInt,
resolve: () => getTodos().length,
},
completedCount: {
type: GraphQLInt,
resolve: () => getTodos('completed').length,
},
},
interfaces: [nodeInterface],
});
const Root = new GraphQLObjectType({
name: 'Root',
fields: {
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
node: nodeField,
},
});
You can see that on the GraphQLTodo field, it has text and complete fields with resolve function passed an obj parameter, how is obj passed there? is it from GraphQLUser resolve? I've read on docs that source(in this case obj) - The object resolved from the field on the parent type. is it not from the root query? how is obj here created?
The Connection
Here is where (some of) the magic happens:
const {
connectionType: TodosConnection,
edgeType: GraphQLTodoEdge,
} = connectionDefinitions({
name: 'Todo',
nodeType: GraphQLTodo,
});
You have now told GraphQL that a TodosConnection is going to be made up of GraphQLTodo nodes. Now, let's take a look at where the objects are actually fetched for the connection in your GraphQLUser object, which is on the todos field:
todos: {
type: TodosConnection,
args: {
status: {
type: GraphQLString,
defaultValue: 'any',
},
...connectionArgs,
},
resolve: (obj, {status, ...args}) =>
connectionFromArray(getTodos(status), args),
},
So where does the object come from? The key part here is the getTodos function, which is responsible for actually getting an array of the objects from your data source. Since this field is a TodosConnection and we've already specified in the connection definitions that the nodes are GraphQLTodos, GraphQL knows that the text and complete fields are resolved by getting (in this case) identically named fields on the objects that have been returned. In other words, the returned object is passed to the resolve method on each field.
Querying the Root
You have two fields exposed on Root: viewer and node. Ignoring node for a moment, you have just one way to actually query todos. Since viewer is of type GraphQLUser, and GraphQLUser has that todos field, they can be fetched only as a subfield of viewer, like this:
{
viewer {
todos(first: 10) {
edges {
# each node is a Todo item
node {
text
complete
}
}
}
}
}
Mystery of the Node
But what about that node field? Relay wants to be able to fetch any object using a top-level query, i.e. on your Root field, when given a unique globalId, which is just a base64 encoding of the type name and the id, so Todo:1 is encoded to VG9kbzox. This is set up in the nodeDefinitions (which you haven't included here, but probably have). In those definitions, the globalId is parsed back into the type (Todo) and id (1), and once again you then tell it how to fetch the correct object from your data source. It might look something like:
const { nodeInterface, nodeField } = nodeDefinitions(
(globalId) => {
const { type, id } = fromGlobalId(globalId);
if (type === 'Todo') {
return getTodo(id)
} else if (type === 'User') {
return getUser(id)
}
...
Because you're implementing the nodeInterface in both your GraphQLTodo and GraphQLUser types, Relay will be able query for either of them from the Root's node field.

Each Node of data perform separate database request

below is the GraphQLObject Fields
userId: {
type: GraphQLID,
resolve: obj => {
console.log(obj._id);
return obj._id;
}
},
email: { type: GraphQLString },
password: { type: GraphQLString },
firstName: { type: GraphQLString },
lastName: { type: GraphQLString },
mine server sents multiple request equally as of mine documents, here it will send 5 different request.
how can i optimize these request get all data in one request
589800cf39b58b29c4de90dd
--------------------------------
58980673e7c9a733009091d1
--------------------------------
58985339651c4a266848be42
--------------------------------
589aac5f884b062b979389bc
--------------------------------
589aad9d24989c2d50f2a25a
In such a case you could create a query method which would accept an array as a parameter, which would be an array of IDs in this case.
getUsers: {
type: new GraphQLList(User),
args: {
ids: {
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLID)))
}
},
resolve: (root, args, context) => {
let query = 'SELECT * FROM users WHERE id = ANY($1)';
return pgClient.query(query, [args.ids], (err, result) => {
// here you would access all returned rows in the result object
console.log(result);
});
}
}
The query variable would differ depending on what database you are using. In this example I have used the node-postgres module for PostgreSQL. However, the concept is the same - use array of ids to perform single query returning all users.
And then you could call that query:
query getUsers($ids: [ID!]!) {
getUsers(ids: $ids){
id
email
...
}
}
// and the variables below
{
ids: ['id#1', 'id#2', 'id#3']
}
This is a job for Dataloader, a library from Facebook specifically for batching together queries like this:
https://github.com/facebook/dataloader

GraphQL : Implementing windowed pagination for regular list

I'm trying to implement a windowed pagination using a "List". I don't need the cursor based solution with connections, because I need to show numbered pages to the user.
There are "User" and "Post" objects."User" has one-to-many relation to "Post".
Using graphql-js for schema,
here is my schema for userType and postType:
var userType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: globalIdField('User'),
posts: {
type: new GraphQLList(postType),
args: {
page:{
type: GraphQLInt,
defaultValue: 0
}
},
resolve: (_, args) => {
//code to return relevant result set
},
},
totalPosts:{
type: GraphQLInt,
resolve: () => {
//code to return total count
}
},
}),
interfaces: [nodeInterface],
});
var postType = new GraphQLObjectType({
name: 'Post',
fields: () => ({
id: globalIdField('Post'),
name: {type: GraphQLString},
//other fields
}),
interfaces: [nodeInterface],
});
Please notice the "totalPosts" field in "userType". Since there is going to be other Lists for the user,with the same paging needs, I'm going to end up maintaining lot of "total{Type}" variables in the fragment. This can be solved if I can send the totalCount within the List result somehow.
https://github.com/facebook/graphql/issues/4 this issue talks about implementing a wrapper over the List to include the totalCount in the result set.
I tried creating a wrapper like this:
var postList = new GraphQLObjectType({
name: 'PostList',
fields:()=>({
count: {
type: GraphQLInt,
resolve: ()=>getPosts().length //this is total count
},
edges: {
type: new GraphQLList(postType),
resolve: () => {
return getPosts() ; // this is results for the page, though I don't know how to use 'page' argument here
},
}
}),
interfaces: [nodeInterface],
});
but how should I connect this to the userType's posts field? And how can I use a 'page' argument on this wrapper, like I have in original userType?
how should I connect this to the userType's posts field? And how can I use a 'page' argument on this wrapper, like I have in original userType?
One simple way to implement what you're trying to do is to define a dumb wrapper type postList like this:
var postList = new GraphQLObjectType({
name: 'PostList',
fields:()=>({
count: { type: GraphQLInt },
edges: { type: new GraphQLList(postType) }
// Consider renaming 'edges'. In your case, it's a list, not a
// connection. So, it can cause confusion in the long run.
}),
});
Then in the userType definition, add a field of that wrapper type and define its resolve function like below. As for argument page, just describe it while defining the field type posts.
posts: {
type: postList,
args: {
page:{
type: GraphQLInt,
defaultValue: 0
},
...otherArgs
},
resolve: async (_, {page, ...otherArgs}) => {
// Get posts for the given page number.
const posts = await db.getPosts(page);
// Prepare a server-side object, which corresponds to GraphQL
// object type postList.
const postListObj = {
count: posts.length,
edges: posts
};
// Consider renaming 'edges'. In your case, it's a list, not a
// connection. So, it can cause confusion in the long run.
},
},

Resources