How to handle Union or Interface in graphQL mutations? - graphql

I got the following schema :
type Vehicle {
id: ID!
name: String!
color: String!
}
input AddVehicle {
name: String!
color: String!
}
input UpdateVehicle {
id: ID!
name: String!
}
Now I would like to add some properties to my vehicles, depending of the Vehicle model, like
type CarProperties {
wheelSize: Int!
doors: Int!
}
type BoatProperties {
length: Int!
}
union VehicleProperties = CarProperties | BoatProperties
type Vehicle {
[ ... ]
properties: vehicleProperties!
}
So it's quite straightforward to write the queries, but I'm struggling when it comes to make mutations...
AFAIK graphQL inputs does not implement unions or interface (There is a related thread here https://github.com/graphql/graphql-spec/issues/488)
So the workaround I see here is to duplicate my inputs, like :
input addBoatVehicle {
name: String!
color: String!
properties: BoatProperties!
}
and so on with updateBoatVehicle, addCarVehicle, updateCarVehicle.
But if I get a lot of vehicle models, or maybe a third or a fourth mutation, I'm afraid it becomes cumbersome quickly.
Is there any recommended way to manage this case ?

Creating separate mutations is the proper solution. You can alleviate some of the pain by making your mutations incredibly lightweight and refactor out processing of those items to a separate function.
function addVehicle(input) {
// disambiguate the input type
}
function updateVehicle(input) {
// dismabiguate the input type, preferably in its own refactor function so
// it can be used above too!
}
const resolvers = {
Mutation: {
addBoat: (parent, boatInput) => { return addVehicle(boatInput) },
addCar: (parent, carInput) => { return addVehicle(carInput) },
updateBoat: (parent, boatInput) => { return updateVehicle(boatInput) },
updateCar: (parent, carInput) => { return updateVehicle(carInput) },
}
}

Related

How to add graphql pagination on nested object

I am writing a graphql api endpoint where I have customer details as below.
{
customer_name:
customer_address:
[
{
address_1:
},
{
address_2:
},
]
}
I need to apply pagination on customer_address which is a list.
Is this possible? Or I can do it only at top level record? Please let me know what would be the best way to do it?
You can possible by using resolver like following
input PageInput{
limit: Int!
page: Int!
}
type CustomerAddressPage {
totalCount: Int!
edges: [CustomerAddress!]!
}
type CustomerAddress {
address: String
}
type Customer {
customerName: String
customerAddress(input: PageInput!): CustomerAddressPage
}
I don't know what kind of framework you use, in nestjs you can be done as follows.
#Resolver(() => Customer)
export class CustomerResolver {
#ResolveField(() => CustomerAddressPage)
customerAddress(
#Parent() customer: Customer,
#Args('input') input: PageInput,
): Promise<CustomerAddressPage> {
return {
};
}
}

GraphQL query with multiple nested resolvers and mapping fields to arguments

From GraphQL Client's perspective, how do I perform a query with multiple nested resolvers where the fields from the parent are passed as arguments to the child resolver?
Here is a minimal example:
GraphQL Schema:
type Author {
id: ID!
name: String!
}
type Book {
id: ID!
title: String!
releaseDate: String!
}
type Query {
// Returns a list of Authors ordered by name, 'first' indicates how many entries to return
getAllAuthors(first: Int!): [Author]!
// Returns a list of Books ordered by releaseDate, 'first' indicates how many entries to return
getBooksByAuthorId(first: Int! authorId: ID!): [Book]!
}
Is it possible to write a query to get all authors and their last released book? Something around the lines:
query GetAuthorsWithLastBook($first: Int!) {
getAllAuthors(first: $first) {
authorId: id
name
lastBook: getBooksByAuthor(1, authorId) {
title
}
}
}
In the example above, I attempted to alias getAllAuthors.id as authorId and pass the alias down as argument to getBooksByAuthor(...) but that didn't work.
The key aspect of the problem is that I don't know the authorIds beforehand. I could fetch the authors first and build a query to fetch their last book but that will result in multiple queries and that is something I would like to avoid.
Update
A Java Kickstarter example is available here: https://www.graphql-java-kickstart.com/tools/schema-definition/
yes, on the graphql definition, you need to add lastBook in the Author
type Author {
id: ID!
name: String!
lastBook: [Book]
}
Next up u need to write the resolver for the lastBook
const resolvers = {
Query: {
Author {
lastBook: (parent, args) {
const userId = parent.id;
return getBooksByAuthor(userId, 1);
},
}
}
};

Input type for array of objects as mutation input messes with optimistic response

On the server I built a schema where I used and input type to obtain the ability to pass an array of objects. I've done this in a couple of places but this is by far the most simple one:
export default gql`
input SkillInput{
skill: String!
}
extend type Mutation {
createSkill(input: [SkillInput]): [Skill]!
}
type Skill {
id: ID!
skill: String!
created_at: DateTime!
}
`;
On the frontend, I'm able to execute the mutation with said array of objects just fine. The issue comes when I try to incorporate optimistic response.
This isthe mutation in question:
this.$apollo
.mutate({
mutation: CREATE_SKILL_MUTATION,
variables: { input: skillArrOfObj },
optimisticResponse: {
__typename: "Mutation",
createSkill: skillArrOfObj.map(entry => ({
__typename: "Skill",
id: -1,
skill: entry.skill
}))
},
update: (store, { data: { createSkill } }) => {
const data = store.readQuery({
query: SKILLS_QUERY
});
console.log(createSkill);
data.skills.push(...createSkill);
store.writeQuery({
query: SKILLS_QUERY,
data
});
}
})
I've tried to add to each entry of skillArrOfObj the __typename and id, however the mutation fails.
Another thing to mention is that update runs twice and the log on createSkill yields two different results on update:
First run
{__typename: "Skill", id: -1, skillObj: Array(2)}
id: -1
skillObj: (2) [{…}, {…}]
__typename: "Skill"
Second run shows an array of just the id and the __typename with no skill attribute
Is there a special __typename needed for arrays? Or something I need to do before running the mutation?

how can I fetch data from graphql in my resolver

Within my resolver I seem to be unable to fetch connected data
this works in the graphql playground (prisma) but I am unsure of the syntax about how to form a resolver in apollo server
// my typedef for activity is
type Activity {
id: ID! #id
ActivityType: ActivityType!
title: String!
date: DateTime
user: User!
distance: Float!
distance_unit: Unit!
duration: Float!
elevation: Float
elevation_unit: Unit
createdAt: DateTime! #createdAt
updatedAt: DateTime! #updatedAt
// and my resolver currently looks like this
async activity(parent, args, ctx, info) {
const foundActivity = await ctx.db.query.activity({
where: {
id: args.id
}
});
// todo fetch user data from query
console.log(foundActivity);
}
// where db has been placed onto the ctx (context)
// the CL gives all of the data for Activity apart from the user
// in the playground I would do something like this
query activity {
activity(where: {
id: "cjxxow7c8si4o0b5314f9ibek"
}){
title
user {
id
name
}
}
}
// but I do not know how to specify what is returned in my resolver.
console.log(foundActivity) gives:
{ id: 'cjxxpuh1bsq750b53psd2c77d',
ActivityType: 'CYCLING',
title: 'Test Activity',
date: '2019-07-10T20:21:27.681Z',
distance: 13.4,
distance_unit: 'KM',
duration: 90030,
elevation: 930,
elevation_unit: 'METERS',
createdAt: '2019-07-10T20:48:50.879Z',
updatedAt: '2019-07-10T20:48:50.879Z' }
Prisma is the DB ORM and then I have an Apollo-Server 2 server running on top of that. Unfortunately, stack overflow also thinks that there is too much code on this post so I will have to waffle on about inconsequential gibberish due to the fact that their system can't handle it.
You will have to implement a resolver for Activity.user. Unfortunately your entity does not seem to contain a reference to the user. First, add the user connection to your Prisma data model. Then implement a resolver for Activity.user. I am not very familiar with Prisma 1 but this naive implementation should already do what you want:
let resolvers = {
Query: {
// ...
},
Activity: {
user(parent, args, ctx) {
return ctx.db.query.activity({ id: parent.id }).user();
}
}
}
Find out more about resolving relations in Prisma here
So the answer was incredibly simple:
I just add a second argument to the query (after the "where" with a gql tag of the data shape to be returned so my code now looks like:
const foundActivity = await ctx.db.query.activity(
{
where: {
id: args.id
}
},
`{id title user { id name }}`
);

Cascade delete related nodes using GraphQL and Prisma

I'm trying to figure out cascade deletion in GraphQL.
I'm attempting to delete a node of type Question, but type QuestionVote has a required relation to Question. I'm looking for a way to delete a Question and all its votes at once.
Mutation for deleting a Question:
type Mutation {
deleteQuestion(where: QuestionWhereUniqueInput!): Question!
}
And its resolver (I'm using Prisma):
function deleteQuestion(parent, args, context, info) {
const userId = getUserId(context)
return context.db.mutation.deleteQuestion(
{
where: {id: args.id}
},
info,
)
}
How can I modify that mutation to also delete related QuestionVote nodes? Or should I add a separate mutation that deletes one or multiple instances of QuestionVote?
In case it's important, here are the mutations that create Question and QuestionVote:
function createQuestion(parent, args, context, info) {
const userId = getUserId(context)
return context.db.mutation.createQuestion(
{
data: {
content: args.content,
postedBy: { connect: { id: userId } },
},
},
info,
)
}
async function voteOnQuestion(parent, args, context, info) {
const userId = getUserId(context)
const questionExists = await context.db.exists.QuestionVote({
user: { id: userId },
question: { id: args.questionId },
})
if (questionExists) {
throw new Error(`Already voted for question: ${args.questionId}`)
}
return context.db.mutation.createQuestionVote(
{
data: {
user: { connect: { id: userId } },
question: { connect: { id: args.questionId } },
},
},
info,
)
}
Thanks!
You can set up cascade deletion by modifying your datamodel.
Given your question, I assume your datamodel looks somewhat like this:
type Question {
id: ID! #unique
votes: [QuestionVote!]! #relation(name: "QuestionVotes")
text: String!
}
type QuestionVote {
id: ID! #unique
question: Question #relation(name: "QuestionVotes")
isUpvote: Boolean!
}
Then you have to add the onCascade: DELETE field to the #relation directive like so:
type Question {
id: ID! #unique
votes: [QuestionVote!]! #relation(name: "QuestionVotes" onDelete: CASCADE)
text: String!
}
type QuestionVote {
id: ID! #unique
question: Question #relation(name: "QuestionVotes")
isUpvote: Boolean!
}
Now, every time a Question node is deleted, all related QuestionVote nodes are also deleted.
Note: If omitting onDelete, the value is automatically set to onDelete: SET_NULL by default. This means that deleting a node results in setting the other side of the relation to null.
You can read more about cascading deletes in Prisma in the documentation.

Resources