Following this example here:
https://github.com/prisma/prisma-examples/blob/latest/javascript/graphql-sdl-first/src/schema.js
Let's say I have a mutation where I want to update multiple users by passing their ids and emails accordingly. I know the updateMany would probably be the most suitable option, but since different users would have different values, not sure how to pass that without calling multiple resolvers separately. Something like this:
updateUsers: (_parent, args, context) => {
return context.prisma.user.updateMany({
where: {
id: { in: args.userIds },
},
data: {
email: ??? <--- use args.emails here
}
})
}
or should I just run multiple mutations:
updateUsers: async (_parent, args, context) => {
try {
args.emails.forEach(email => {
const user = await context.prisma.user.update({
where: {
id: { in: args.userIds },
},
data: {
email: args.email
}
})
return user;
}
} catch (error) {
console.log(error)
}
}
not sure if the last example would even work because of multiple return statements, since all the resolvers have a return statement, how can I run multiple queries/mutations?
updateMany allows you to bulk update all rows matching certain conditions with the same data. It does not help you in this case.
You could loop over an array of users to update id and email of each of them. Your approach will not work however, as you seem to have two arrays (a list of user IDs and a list of email addresses). However, instead of fixing your code I'd suggest to change the GraphQL interface you've defined.
Option 1) is close to what you did. You keep a bulk mutation updateUsers, but instead of two lists (a list of user IDs and a list of email addresses), it should accept a list of users, each of them having an user ID and an email address.
To improve performance you could use Promise.all and not wait for each update to happen, before starting the next one.
Option 2) is the preferable. Instead of a bulk mutation updateUsers, I would create a mutation updateUser that updates only one user. If client wants to update multiple users in the same request, they can! A single GraphQL request can contain multiple mutations.
Related
What is a good pattern for GraphQL queries?
Consider the following (id and email are guaranteed unique).
First a pattern where the query name tells you what argument to expect:
getUserById(id)
getUserByEmail(email)
vs a single query that can handle either variable:
getUser({ id })
getUser({ email })
In this scenario, the resolver would pick the field and query by that.
Some of the pros/cons:
pro: using one query for both types enables more flexibility down the line to add more fields rather than creating new queries for each.
pro: using one query makes it easier to understand vs hunting different interfaces.
pro: if the consumer wanted to dynamically change the type you wouldn't have to write another query entirely.
con: validation would have to be handled manually in the resolver.
con: the backend complexity for the resolvers is increased.
I have some similar queries in a project and I tend to use the second pattern. Since I use the knex orm my resolver complexity doesn't increase. Just:
getUser: (parent,args,context) => knex('user').where(args).first()
Thanks to the GraphQL query definition I can depend on the args being limited to certain keys with specific types.
query getUser(id: ID, email: emailType): User
For args where the results are guaranteed unique this is fine. If the args might yield a non-unique answer then you may wish to handle them differently. There's also the issue of what to do if id and email don't point to the same record in your case.
Then of course since both id and email are optional there's the diminutive case where no argument is provided at all. GraphQL doesn't provide a way to specify at least one argument must be provided. Each argument is required or not individually.
You may want to handle this differently if you're not personally writing both the resolvers and the client side queries since random clients may send queries that violate your assumptions.
If you can have a query by id as the default pattern for all objects and for any special case there will be a descriptive query name.
This takes care of 90% of your need through the default pattern.
Just make an exception when you needed.
getUser(id)
getUserByEmail(email)
getCompany(id)
getCompanyByName(name)
#Query(() => UserDto, { description: 'Get other user info' })
async user(#UserDecorator() currentUser: User, #Args('id') id: string) {
return await this.prisma.user.findUnique({ where: { id } });
}
#Query(() => UserDto, { description: 'Get other user info by Email' })
async userByEmail(#UserDecorator() currentUser: User, #Args('email') email: string) {
return await this.prisma.user.findUnique({ where: { email } });
}
Alternatively, as mentioned already, pass in all variables as optional and let the backend handle it.
// everything is optional, and each unique
#InputType()
export class UserWhereUniqueInput implements Partial<User> {
#Field(() => ID, { nullable: true })
id?: string;
#Directive('#lowercase')
#Field(() => String, { nullable: true })
email?: string;
#Directive('#lowercase')
#Field(() => String, { nullable: true })
username?: string;
}
// on the backend, just let the ORM take the input as received
async user(userWhereUniqueInput: UserWhereUniqueInput): Promise<User | null> {
return this.prisma.user.findUnique({ where: userWhereUniqueInput });
}
Reference:
https://github.com/neekware/fullerstack
https://github.com/neekware/fullerstack/blob/916cf1031af0e077d339a3fbdad04eeb32fc3884/libs/nsx-user/src/lib/user.model.ts#L155
https://github.com/neekware/fullerstack/blob/916cf1031af0e077d339a3fbdad04eeb32fc3884/libs/nsx-user/src/lib/user.resolver.ts#L64
https://github.com/neekware/fullerstack/blob/916cf1031af0e077d339a3fbdad04eeb32fc3884/libs/nsx-user/src/lib/user.service.ts#L40
I using https://hygraph.com/, and I want insert (create many products) in a single GraphQL request.
At the moment I know how to insert one product:
mutation {
createProduct(data: { title: "Face Mask", slug: "dfavce-mask", price: 1000 }) {
id
}
}
I read the documentation, but I didn't see information about bulk creation records.
Link for hygraph documentation:
https://hygraph.com/docs/api-reference/content-api/mutations#create-entries
The top-level query you show is just a query against the Mutation type (or another type specified in the schema). Like any other query, it can have multiple fields. At a technical level, the only special thing about GraphQL mutations is that, if you do have multiple fields, they execute sequentially.
Also like other queries, if you want to request the same field multiple times (run similarly-named mutations) you need to use an alias to disambiguate the results.
mutation {
createFaceMask: createProduct(data: { title: "Face Mask" }) { id }
createHandSanitizer: createProduct(data: { title: "Hand Sanitizer" }) { id }
}
I have a problem I don't know how to solve properly.
I'm working on a project where we use a graphql server to communicate with different apis. These apis are old and very difficult to update so we decided to use graphql to simplify our communications.
For now, two apis allow me to get user data. I know it's not coherent but sadly I can't change anything to that and I need to use the two of them for different actions. So for the sake of simplicity, I would like to abstract this from my front app, so it only asks for user data, always on the same format, no matter from which api this data comes from.
With only one api, the resolver system of graphql helped a lot. But when I access user data from a second api, I find very difficult to always send back the same object to my front page. The two apis, even though they have mostly the same data, have a different response format. So in my resolvers, according to where the data is coming from, I should do one thing or another.
Example :
API A
type User {
id: string,
communication: Communication
}
type Communication {
mail: string,
}
API B
type User {
id: string,
mail: string,
}
I've heard a bit about apollo-federation but I can't put a graphql server in front of every api of our system, so I'm kind of lost on how I can achieve transparency for my front app when data are coming from two different sources.
If anyone has already encounter the same problem or have advice on something I can do, I'm all hear :)
You need to decide what "shape" of the User type makes sense for your client app, regardless of what's being returned by the REST APIs. For this example, let's say we go with:
type User {
id: String
mail: String
}
Additionally, for the sake of this example, let's assume we have a getUser field that returns a single user. Any arguments are irrelevant to the scenario, so I'm omitting them here.
type Query {
getUser: User
}
Assuming I don't know which API to query for the user, our resolver for getUser might look something like this:
async () => {
const [userFromA, userFromB] = await Promise.all([
fetchUserFromA(),
fetchUserFromB(),
])
// transform response
if (userFromA) {
const { id, communication: { mail } } = userFromA
return {
id,
mail,
}
}
// response from B is already in the correct "shape", so just return it
if (userFromB) {
return userFromB
}
}
Alternatively, we can utilize individual field resolvers to achieve the same effect. For example:
const resolvers = {
Query: {
getUser: async () => {
const [userFromA, userFromB] = await Promise.all([
fetchUserFromA(),
fetchUserFromB(),
])
return userFromA || userFromB
},
},
User: {
mail: (user) => {
if (user.communication) {
return user.communication.mail
}
return user.mail
}
},
}
Note that you don't have to match your schema to either response from your existing REST endpoints. For example, maybe you'd like to return a User like this:
type User {
id: String
details: UserDetails
}
type UserDetails {
email: String
}
In this case, you'd just transform the response from either API to fit your schema.
I've been playing with GraphQL recently, and am currently learning about mutations. I'm a bit confused with something. I have a model Post with relation Comments. I have a mutation that looks like this:
mutation addCommentToPost {
updatePost(
id: "POST-1",
comments: [{
body: "Hello!"
}]
) {
id,
comments {
id,
body
}
}
}
The problem is, whenever I run this, it seems to remove all the comments and sets the comments to only the one I just added. To be more specific, how do I write a mutation that pushes to the comments array rather than replacing it?
You are using a mutation called updatePosts, which I assume (based on the name) simply updates a post by replacing the fields that are passed. If you want to use the updatePosts mutation to add a comment, you will first have to query for the post to get the current list of comments, add your comment to the end, and then call updateComment with the entire list of comments (including the one that you just added to the end).
However, this isn't really a good solution, especially if the list of comments is potentially very long. If you have the ability to change the GraphQL server, you should create a new mutation on the server with a signature like addComment(postId: ID, comment: CommentInput). In the resolve function for that mutation, simply add the comment that is passed to the end of the list of current comments.
// resolver for addComment:
addComment(root, args) {
// validate inputs here ...
const post = db.getPost(args.postId);
post.comments.append(args.comment);
db.writePost(post.id, post);
}
db.getPost and db.writePost are functions you have to define yourself to retrieve/write a post from/to wherever you store it.
It's important to note that unlike a SQL or Mongo query, a GraphQL mutation itself doesn't have any meaning without the resolve functions. What the mutation does is defined entirely inside its resolve function. Mutation names and arguments only gain meaning together with the resolve function. It's up to you (or the GraphQL server developers in your company) to write the resolve functions.
The way this situation is currently solved in the Graphcool API is to use a create mutation for the Comment that links to the Post. This is called a nested connect mutation.
This is how it would look like:
mutation {
createComment(
text: "Hello!"
postId: "POST-1"
) {
id
text
post {
comments {
id
}
}
}
}
In the future, other nested arguments like comments_set or comments_push could be introduced, then pushing would be possible like this:
mutation addCommentToPost {
updatePost(
id: "POST-1",
comments_push: [{
body: "Hello!"
}]
) {
id,
comments {
id,
body
}
}
}
Disclosure: I work at Graphcool.
You can use those code as an example for mutation.
module.exports = (refs) => ({
type: refs.commentType,
args: {
id: {
type: GraphQLString
},
body: {
type: GraphQLString
}
},
resolve: (parent, args, root) => {
return createUser(args);
}
});
I have set up a GraphQL endpoint that returns me a client
query {
client(id:1) {
clientId
}
}
and another that returns a list of clients
query {
clients {
clientId
}
}
I have 2 backing db queries for these 2 graphql queries, but is there a way to have a single query for both? Or what is the graphql way of handling this?
The GraphQL way of handling this is exactly how you have done it. You usually need separate fields in your schema to handle retrieving one item vs multiple, just like you would have separate endpoints for these in a REST API.
You can have a single end point that returns a GraphQLList Type. This list can contain either one object or however many.
In your case, that single end point will be clients. You just have to use your backend to see if the consumer of your GraphQL API has supplied any arguments i.e. clientId. If the clientId has been supplied, filter your clientRepo by that supplied clientId. Otherwise return the whole list (repo) of clients.
clients: {
type: new GraphQLList(clientType), <--- Note this is a GraphQLList type
args: {
id: {
type: GraphQLInt
},
},
resolve: (parent, args) => {
if (args.id) {
return clientRepo.find(args.id);
}
return clientRepo.findAll();
}
}
You might want to visit the following links:
https://jaketrent.com/post/return-array-graphql/
https://stackoverflow.com/a/52773152/4195803