Nested query for the new GraphQL buildSchema - graphql

How can I make resolver for my friendList with the new GraphQL Schema language? friendList have an array of people _id.
My new people type with GraphQL Schema language:
const schema = buildSchema(`
type People {
_id: String
firstName: String
lastName: String
demo: String
friendList: [People]
}
type Query {
getPeople(_id: String): People
}
`);
My old people type with GraphQLObjectType:
const PeopleType = new GraphQLObjectType({
name: 'People',
fields: () => ({
_id: {
type: GraphQLString,
},
firstName: {
type: GraphQLString,
},
lastName: {
type: GraphQLString,
},
friendList: {
type: new GraphQLList(PeopleType),
// pass #friends parentValue
resolve: ({ friends }) {
return People.find({ _id: { $in: friends } }).then(res => res);
},
}),
});
I want to achieve this query:
{
people(_id: "ABC123") {
firstName
lastName
friendList {
firstName
lastName
}
}

Your resolver should return a new instance of a class as explained in the updated GraphQL documentation: http://graphql.org/graphql-js/object-types/.
class People {
friendList () {}
}
var rootValue = {
getPeople: function () {
return new People();
}
}

I couldn't find a way to do this with graphql-js so I switched to a similar implementation from the folks at Apollo called graphql-tools.
http://dev.apollodata.com/tools/graphql-tools/generate-schema.html
They have a function called makeExecutableSchema that takes a schema written in the GraphQL Schema language and combines it with (nested) resolver functions. This fixes the issue and is actually pretty simple to integrate with if your code uses the GraphQL Schema language.
They also have another agnostic tool for exposing this new schema that replaces express-graphql, its GraphQL-server:
http://dev.apollodata.com/tools/graphql-server/index.html
Check out the docs and hopefully this gives you the flexibility to write complex code using the GraphQL Schema language syntax!

Related

Prisma and ApolloClient: Prevent overwriting the include conditions at the backend by the frontend for relations

I have a problem, thx for any help.
With prisma we can use include with where conditions for models with a relation. If I make include conditions I get the right result. If I return it to the frontend it gets overwritten. I want to return exact my result from the backend.
I have at the frontend a query (ApolloClient, gql) like. It will return an array of comments for each post, I just want to have the first Comment for each post.
const POSTS = gql`
query posts {
posts(postId: $postId) {
id
comments{ // at the backend I have conditions for the comments
id
}
}
}
`;
Backend: Primsa and graphql nexus
Prisma Schema
model Post {
id String #id #default(cuid())
comments Comment[]
}
model Comment {
id String #id #default(cuid())
post Post #relation(fields: [postId], references: [id])
postId String
}
Nexus Model
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.comments()
})
const Comment = objectType({
name: 'Comment',
definition(t) {
t.model.id()
t.model.post()
t.model.postId()
})
Resolver
export const posts = queryField('posts', {
type: 'Post',
list: true,
args: {
...
},
resolve: async (_parent, args: any, { prisma, request }, info) => {
const posts = await prisma.post.findMany({
include: {
comments: {
take: 1
}
}
})
console.log(posts)
//Perfect result I want to return the include condition. But at the frontend I have all
//comments
return posts
},
})
The console.log(posts) is exact what I want to return!. Every post has an Array of ONE Comment.
I return the posts and at the frontend every post has an Array of ALL Comments, what I don't want. How can I prevent that the frontend query overwrite the backend return? The fields are the same.
I can't add a comment, so I am adding this to another answer.
Like I said with my PrismaSelect plugin, you can't use nexus-plugin-prisma t.model, t.crud. You will need to use Pal.Js CLI to autoGenerate all CRUD and ObjectTypes for all models.
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.comments() // this field will overwritten by next one so this not needed
t.list.field('comments', {
type: 'Comment',
list: true,
resolve: (parent, args, { prisma }) => {
// here parent type include all other fields but not this field
return prisma.comment.findMany({ // this query is very wrong will case N+1 issue
where: {
postId: parent.id,
},
take: 1,
})
},
})
})
Example
model User {
id Int #default(autoincrement()) #id
createdAt DateTime #default(now())
email String #unique
name String?
password String
posts Post[]
comments Comment[]
}
model Post {
id Int #default(autoincrement()) #id
published Boolean #default(false)
title String
author User? #relation(fields: [authorId], references: [id])
authorId Int?
comments Comment[]
}
model Comment {
id Int #default(autoincrement()) #id
contain String
post Post #relation(fields: [postId], references: [id])
postId Int
author User? #relation(fields: [authorId], references: [id])
authorId Int?
}
Here is my Pal.Js CLI generated type for Post model
import { objectType } from '#nexus/schema'
export const Post = objectType({
name: 'Post',
definition(t) {
t.int('id', { nullable: false })
t.boolean('published', { nullable: false })
t.string('title', { nullable: false })
t.field('author', {
nullable: true,
type: 'User',
resolve(parent: any) {
return parent['author']
},
})
t.int('authorId', { nullable: true })
t.field('comments', {
nullable: false,
list: [true],
type: 'Comment',
args: {
where: 'CommentWhereInput',
orderBy: 'CommentOrderByInput',
cursor: 'CommentWhereUniqueInput',
take: 'Int',
skip: 'Int',
distinct: 'CommentDistinctFieldEnum',
},
resolve(parent: any) {
return parent['comments']
},
})
},
})
when you use my Pal.js CLI, your frontend request will be like this
query {
findOnePost(where: {id: 1}) {
comments(where: {}, take: 1){
id
}
}
}
``
The best way to handle this issue and just query what your frontend request to use my PrismaSelect plugin.
Prisma Select takes the info: GraphQLResolveInfo object in general graphql arguments (parent, args, context, info) to select object accepted by prisma client. The approach allows a better performance since you will only be using one resolver to retrieve all your request. By doing so, it also eliminates the N + 1 issue.
Also, you can use my CLI to autogenerate all CRUD from your schema.prisma file https://paljs.com/generator/nexus
I mean I can add to my Post-ObjectType a field condition like:
const Post = objectType({
name: 'Post',
definition(t) {
t.model.id()
t.model.comments()
t.list.field('comments', {
type: 'Comment',
list: true,
resolve: (parent, args, { prisma }) => {
return prisma.comment.findMany({
where: {
postId: parent.id,
},
take: 1,
})
},
})
})
This is working. But if I understood it correct I have for every post one extra request. But I have already at the mutation resolver the right result. And I don't have the comments field at the parent (t.list.field- resolver)

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.

Should/Can I make changes to data on nested resolvers?

Say I have a MemberType like this
import {
GraphQLID,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLString
} from 'graphql'
import readPhoneNumbersByMemberId from '../resolvers/readPhoneNumbersByMemberId'
import PhoneNumberType from './PhoneNumberType'
const MemberType = new GraphQLObjectType({
name: 'Member',
fields: {
id: {
type: new GraphQLNonNull(GraphQLID)
},
firstName: {
type: new GraphQLNonNull(GraphQLString)
},
lastName: {
type: new GraphQLNonNull(GraphQLString)
},
phoneNumbers: {
type: new GraphQLList(PhoneNumberType),
resolve: readPhoneNumbersByMemberId
}
}
})
export default MemberType
and a PhoneNumberType like this
import {
GraphQLNonNull,
GraphQLObjectType,
GraphQLString
} from 'graphql'
const PhoneNumberType = new GraphQLObjectType({
name: 'PhoneNumber',
fields: {
value: {
type: new GraphQLNonNull(GraphQLString)
}
}
})
export default PhoneNumberType
and a QueryType like this
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
readMembers: {
type: new GraphQLList(MemberType),
resolve: readMembers
}
}
})
Now, if I query a GraphQL schema to retrieve a member, the resolve functions readMembers and readPhoneNumbersByMemberId will be invoked to fetch data from a source.
Is it possible to use the same mechanism on mutations?
You can but you should not. field resolvers are really made to fetch data. Putting create/update/delete logic inside of nested resolvers could quickly become problematic and introduce the same problems you would like to solve with graphql.
It is so much discouraged to the point that facebook created a whole special type called input for any composite objects that's sent as arguments to mutations (or query).
If you can explain the benefits of splitting the mutation logic, it might be a good RFC for GraphQL spec to accept mutate function for every field, this way you will keep the mutation logic separate while achieving the same nested nature.
If you still like to try that, you can just return an action field on the returned object from the root resolver and make the changes you want on the nested resolvers only if you see that field. Make sure you return the data that was changed on the nested resolver.

GraphQL pass args to sub resolve

I have a relationship between User and Post. This is how I query the User Posts.
const UserType = new GraphQLObjectType({
name: 'User'
fields: () => ({
name: {
type: GraphQLString
},
posts: {
type: new GraphQLList(PostType),
resolve(parent, args , { db }) {
// I want to get here the args.someBooleanArg
return someLogicToGetUserPosts();
}
}
})
});
The main query is:
const queryType = new GraphQLObjectType({
name: 'RootQuery',
fields: {
users: {
type: new GraphQLList(UserType),
args: {
id: {
type: GraphQLInt
},
someBooleanArg: {
type: GraphQLInt
}
},
resolve: (root, { id, someBooleanArg }, { db }) => {
return someLogicToGetUsers();
}
}
}
});
The problem is the args in the resolve function of the UserType posts is empty object, how do i pass the args from the main query to sub resolves functions?
When resolving the root query you can use object assign to attach the argument to the user object returned.
Then, on the user type, resolve the argument from the root value (first argument of resolve function).
Example:
const queryType = new GraphQLObjectType({
name: 'RootQuery',
fields: {
users: {
type: new GraphQLList(UserType),
args: {
id: {
type: GraphQLInt
},
someBooleanArg: {
type: GraphQLInt
}
},
resolve: (root, { id, someBooleanArg }, { db }) => {
return Promise.resolve(someLogicToGetUsers()).then(v => {
return Object.assign({}, v, {
someBooleanArg
});
});
}
}
}
});
const UserType = new GraphQLObjectType({
name: 'User'
fields: () => ({
name: {
type: GraphQLString
},
posts: {
type: new GraphQLList(PostType),
resolve(parent, args , { db }) {
console.log(parent.someBooleanArg);
return someLogicToGetUserPosts();
}
}
})
});
You can use the resolver fouth argument, info, to receive the desired variable - from Apollo docs:
Every resolver in a GraphQL.js schema accepts four positional arguments:
fieldName(obj, args, context, info)
{ result }
These arguments have
the following meanings and conventional names:
obj: The object that contains the result returned from the resolver on
the parent field, or, in the case of a top-level Query field, the
rootValue passed from the server configuration. This argument enables
the nested nature of GraphQL queries.
args: An object with the
arguments passed into the field in the query. For example, if the
field was called with author(name: "Ada"), the args object would be: {
"name": "Ada" }.
context: This is an object shared by all resolvers in
a particular query, and is used to contain per-request state,
including authentication information, dataloader instances, and
anything else that should be taken into account when resolving the
query. If you're using Apollo Server, read about how to set the
context in the setup documentation.
info: This argument should only be
used in advanced cases, but it contains information about the
execution state of the query, including the field name, path to the
field from the root, and more. It's only documented in the GraphQL.js
source code.
The info seems to be a very undocumented feature, but I'm using it now with no problems (at least until somebody decide to change it).
Here is the trick:
const UserType = new GraphQLObjectType({
name: 'User'
fields: () => ({
name: {
type: GraphQLString
},
posts: {
type: new GraphQLList(PostType),
resolve(parent, args , { db }, info) {
// I want to get here the args.someBooleanArg
console.log("BINGO!");
console.log(info.variableValues.someBooleanArg);
return someLogicToGetUserPosts();
}
}
})
});

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