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)
When a user creates a data of type Post, I need the graphql server to automatically create a data of type Commit as well. The type Commit connects to type User and type Post. Here is the graphql pattern for type User, type Post, and type Commit:
type User {
id: ID!
username: String!
commits: [Commit!]!
}
type Post {
id: ID!
title: String!
content: String
commits: [Commit!]!
}
type Commit {
id: ID!
user: User!
post: Post!
}
So I made a createPost mutation. The createPost mutation must do two jobs at once: creating the Post, and then creating the Commit. The Commit data should connect to the newly created Post data. Below is the resolver that I have came up with until now:
Mutation: {
createPost: async (_, args) => {
const {user, title, content} = args;
await prisma.createPost({
title,
content
});
const postId = await prisma.post({title}).id();
await prisma.createCommit({
user: {connect: {id: user.id}},
post: {connect: {id: postId }}
});
const newPost = await prisma.post({id:postId});
return newPost;
}
}
Is this an effective way? Or is there a better way to do this?
I think this would work as well:
Mutation: {
createPost: async (_, args) => {
const {user, title, content} = args;
const post = await prisma.createPost({
title,
content
});
await prisma.createCommit({
user: {connect: {id: user.id}},
post: {connect: {id: post.id }}
});
return post;
}
}
Prisma returns the record created so you don't need to query for it again.
I'm struggling to write a nested GraphQL mutation for a React Native app I'm building with GraphQL client being AWS Amplify API. Here's my type definition file
type Game #model {
id: ID!
gameId: String!
players: [Player!]!
}
type Player #model {
id: ID!
username: String!
}
Here's my attempt to create a mutation to create a new Game
import API, { graphqlOperation } from '#aws-amplify/api';
const CreateGame = `
mutation ($gameId: String! $username: String!) {
createGame(input: {
gameId: $gameId,
players: [{ username: $username }]
}) {
id
gameId
players
}
}
`;
const gameObj = {
gameId: 'example_game_id',
username: 'example_username'
};
const queryResp = await API.graphql(graphqlOperation(CreateGame, gameObj));
console.log(queryResp.data);
Here's the error I get
"Validation error of type WrongType: argument 'input' with value 'ObjectValue{objectFields=[ObjectField{name='gameId', value=VariableReference{name='gameId'}}, ObjectField{name='players', value=ArrayValue{values=[ObjectValue{objectFields=[ObjectField{name='username', value=VariableReference{name='username'}}]}]}}]}' contains a field not in 'CreateGameInput': 'players' # 'createGame'"
I'm setting up a react-admin app, that needs to connect with a Hasura Service using a graphql provider. To do so, I need to pass for the provider the endpoint "/v1/graphql" and the query with the selects subfields. Like this:
query MyQuery {
account_customers {
customer_id
email
given_name
}
}
I tried to use the Hasura Provider (https://github.com/hasura/ra-data-hasura/) but the requisitions are going to "/v1/query", and I couldn't found how to change it. Also couldn't figure out how to send my custom query with the subfields.
I also tried to use ra-data-graphql-simple provider, to override a query
to get the resource with the subfields.
/providers/myProvider.js
import buildGraphQLProvider, { buildQuery } from 'ra-data-graphql-simple';
import gql from 'graphql-tag';
const myBuildQuery = introspection => (fetchType, resource, params) => {
const builtQuery = buildQuery(introspection)(fetchType, resource, params);
if (resource === 'account_customers' && fetchType === 'GET_LIST') {
return {
// Use the default query variables and parseResponse
...builtQuery,
// Override the query
query: gql`
query ($id: ID!) {
account_customers {
customer_id
email
name
}
}`,
};
}
return builtQuery;
}
export default buildGraphQLProvider({ buildQuery: myBuildQuery })
App.js
import buildGraphQLProvider from './providers/myProvider.js';
class App extends Component {
constructor() {
super();
this.state = { dataProvider: null };
}
componentDidMount() {
buildGraphQLProvider({ clientOptions: { uri: 'http://localhost:8080/v1/graphql' }})
.then(dataProvider => this.setState({ dataProvider }));
}
render() {
const { dataProvider } = this.state;
return (
<div className="App">
<Admin dataProvider={dataProvider} >
<Resource name="account_customers" list={ListGuesser} />
</Admin>
</div>
);
}
}
export default App;
But I'm getting the error:
"Unknown resource account_customers. Make sure it has been declared on your server side schema. Known resources are "
I have had a similar issue.
The react-admin data provider you're using expects a certain shape of the GraphQL API, so it can get all items, create an item, update an item etc.
I have solved it by making sure that my schema complies with this:
type Query {
Post(id: ID!): Post
allPosts(page: Int, perPage: Int, sortField: String, sortOrder: String, filter: PostFilter): [Post]
_allPostsMeta(page: Int, perPage: Int, sortField: String, sortOrder: String, filter: PostFilter): ListMetadata
}
type Mutation {
createPost(
title: String!
views: Int!
user_id: ID!
): Post
updatePost(
id: ID!
title: String!
views: Int!
user_id: ID!
): Post
deletePost(id: ID!): Post
}
type Post {
id: ID!
title: String!
views: Int!
user_id: ID!
User: User
Comments: [Comment]
}
input PostFilter {
q: String
id: ID
title: String
views: Int
views_lt: Int
views_lte: Int
views_gt: Int
views_gte: Int
user_id: ID
}
type ListMetadata {
count: Int!
}
So in your case, you would need these endpoints (and change the naming in your backend):
AccountCustomer
allAccountCustomers
_allAccountCustomersMeta
updateAccountCustomer
createAccountCustomer
deleteAccountCustomer
etc...
I have a simple query which takes in an ID parameter, but it is not working. It says "TypeError: Cannot read property 'taskId' of undefined" . So I think it does not recognize the 'this' keyword for some reason.
Please take a look:
Apollo query from frontend component:
getCommentsByTask: {
query: GET_COMMENTS_BY_TASK,
variables: {
taskId: this.taskId
},
result({ data }) {
this.getComments = data;
console.log("data", data);
}
}
Defined the query in frontend:
query GET_COMMENTS_BY_TASK($taskId: ID!) {
getCommentsByTask(taskId: $taskId) {
id
parentId
ownerId
text
}
}
Resolver in server:
async getCommentsByTask (_, {taskId}, context) {
const userId = getUserId(context)
const user = await User.findById(userId)
if (!user) return
const comments = await Comment.findById(taskId)
return comments
}
Schema:
type Query {
getCommentsByTask(taskId: ID!): [Comment]
}
Assuming that's a smart query, variables should be a (regular, non-arrow) function if you need access to this.