GraphQL mutation that accepts an array of dynamic size and common scalar types in one request
Asked Answered
D

6

39

I need to be able to create a user and add it's favourite movies (An array of objects with a reference to the Movies collection and his personal rating for each movie) in a single request.

Something that could look like this (pseudocode)

var exSchema = `
 type Mutation {
    addUser(
        name: String!
        favMovies: [{ movie: String! #ref to movies coll
                      personal_rating: Int! # this is different for every movie
                   }]
    ) : User
 }
...
`

What is the graphql way of doing this in a single request? I know I can achieve the result with multiple mutations/requests but I would like to do it in a single one.

Dissuasive answered 19/11, 2016 at 20:31 Comment(3)
Define an input type on your schema that has your name and favMovies. Have addUser() take an instance of that type as its argument. AFAIK, list fields are valid for input types.Chaqueta
Yep, I have to give that a try, I was looking for a better example thoughCalva
In Javascript you can convert a json array to meet this schema like so: favMovies: ${JSON.stringify(moviesArray).replace(/"([^(")"]+)":/g,"$1:")}Foodstuff
D
64

You can pass an array like this

var MovieSchema = `
  type Movie {
   name: String
  }
  input MovieInput {
   name: String
  }
  mutation {
   addMovies(movies: [MovieInput]): [Movie]
  }
`

Then in your mutation, you can pass an array like

mutation {
  addMovies(movies: [{name: 'name1'}, {name: 'name2'}]) {
    name
  }
}

Haven't tested the code but you get the idea

Dissuasive answered 5/1, 2017 at 9:15 Comment(3)
this would be so cool but as I change movies: String to movies: [MovieInput] graphql throws Error: Expected Input type. Of course before this I have declared the Type MovieInputChristal
@octohedron Thank you so much! I've been trying to figure this out.Conjure
@EdmondTamas note that MovieInput is not a type, but rather an input; So like shown in the answer, got to have two definitions, one for the type and another for the input - and the names must be different from each other as well, since two definitions with the same name are not allowed.Byzantine
E
13

I came up with this simple solution - NO JSON used. Only one input is used. Hope it will help someone else.

I had to add to this type:

type Option {
    id: ID!
    status: String!
    products: [Product!]!
}

We can add to mutation type and add input as follows:

type Mutation {
    createOption(data: [createProductInput!]!): Option!
    // other mutation definitions
}

input createProductInput {
    id: ID!
    name: String!
    price: Float!
    producer: ID!
    status: String
}

Then following resolver could be used:

const resolvers = {
    Mutation: {
      createOption(parent, args, ctx, info) {

        const status = args.data[0].status;

        // Below code removes 'status' from all array items not to pollute DB.
        // if you query for 'status' after adding option 'null' will be shown. 
        // But 'status': null should not be added to DB. See result of log below.
        args.data.forEach((item) => {
            delete item.status
        });

        console.log('args.data - ', args.data);

        const option = {
            id: uuidv4(),
            status: status,  // or if using babel    status,
            products: args.data
        }

        options.push(option)

        return option
      },
    // other mutation resolvers
    }

Now you can use this to add an option (STATUS is taken from first item in the array - it is nullable):

mutation{
  createOption(data:
    [{
            id: "prodB",
            name: "componentB",
            price: 20,
            producer: "e4",
            status: "CANCELLED"
        },
        {
            id: "prodD",
            name: "componentD",
            price: 15,
            producer: "e5"
        }
    ]
  ) {
    id
    status
    products{
      name
      price
    }
  }
}

Produces:

{
  "data": {
    "createOption": {
      "id": "d12ef60f-21a8-41f3-825d-5762630acdb4",
      "status": "CANCELLED",
      "products": [
        {
          "name": "componentB",
          "price": 20,
        },
        {
          "name": "componentD",
          "price": 15,
        }
      ]
    }
  }
}

No need to say that to get above result you need to add:

type Query {
    products(query: String): [Product!]!
    // others
}

type Product {
    id: ID!
    name: String!
    price: Float!
    producer: Company!
    status: String
}

I know it is not the best way, but I did not find a way of doing it in documentation.

Emboly answered 1/1, 2019 at 9:15 Comment(0)
D
7

I ended up manually parsing the correct schema, since JavaScript Arrays and JSON.stringify strings were not accepted as graphQL schema format.

const id = 5;
const title = 'Title test';

let formattedAttachments = '';
attachments.map(attachment => {
  formattedAttachments += `{ id: ${attachment.id}, short_id: "${attachment.shortid}" }`;      
  // { id: 1, short_id: "abcxyz" }{ id: 2, short_id: "bcdqrs" }
});

// Query
const query = `
  mutation {
    addChallengeReply(
      challengeId: ${id}, 
      title: "${title}", 
      attachments: [${formattedAttachments}]
    ) {
      id
      title
      description
    }
  }
`;
Dimple answered 8/6, 2018 at 13:24 Comment(1)
this saved my day. Thanks alot for the snippetOt
L
2

What i understand by your requirement is that if you have the following code

const user = {
    name:"Rohit", 
    age:27, 
    marks: [10,15], 
    subjects:[
        {name:"maths"},
        {name:"science"}
    ]
};

const query = `mutation {
        createUser(user:${user}) {
            name
        }
}`

you must be getting something like

"mutation {
        createUser(user:[object Object]) {
            name
        }
}"

instead of the expected

"mutation {
        createUser(user:{
            name: "Rohit" ,
            age: 27 ,
            marks: [10 ,15 ] ,
            subjects: [
                {name: "maths" } ,
                {name: "science" } 
                ] 
            }) {
            name
        }
}"

If this is what you wanted to achieve, then gqlast is a nice tag function which you can use to get the expected result

Simply grab the js file from here and use it as:

const user = {
    name:"Rohit", 
    age:27, 
    marks: [10,15], 
    subjects:[
        {name:"maths"},
        {name:"science"}
    ]
};

const query = gqlast`mutation {
        createUser(user:${user}) {
            name
        }
}`

The result stored in the variable query will be :

"mutation {
        createUser(user:{
            name: "Rohit" ,
            age: 27 ,
            marks: [10 ,15 ] ,
            subjects: [
                {name: "maths" } ,
                {name: "science" } 
                ] 
            }) {
            name
        }
}"
Laxative answered 10/5, 2019 at 18:17 Comment(0)
S
0

For those of you who don't need to pass in an array for one request, and are open to the idea of making a request for every mutation. (I am using Vue3, compisition Api, but React and Angular developers still can understand this).

You cannot for loop the mutation like this:

function createProject() {
    for (let i = 0; i < state.arrOfItems.length; i++) {
      const { mutate: addImplementation } = useMutation(
        post_dataToServer,
        () => ({
          variables: {
            implementation_type_id: state.arrOfItems[i],
            sow_id: state.newSowId,
          },
        })
      );

      addImplementation();
    }
}

this will give you an error, because the mutation must be in the setup(). (here is the error you will recieve: https://github.com/vuejs/vue-apollo/issues/888)

Instead create a child component, and map the array in the parent.

in Parent.vue

<div v-for="(card, id) in state.arrOfItems">
  <ChildComponent
    :id="id"
    :card="card"
  />
</div>

in ChildComponent.vue

recieve props and:

 const { mutate: addImplementation } = useMutation(
    post_dataToServer,
    () => ({
      variables: {
        implementation_id: props.arrOfItems,
        id: props.id,
      },
    })
  );
Shermanshermie answered 11/10, 2021 at 14:47 Comment(0)
V
-1

Pass them as JSON strings. That's what I do.

Vacua answered 19/11, 2016 at 20:33 Comment(3)
@Gazta I have the same problem, have you find a way to pass the JSON array as a string? How did you escape it?Christal
@EdmondTamas added the solutionCalva
JSON.stringify also parses the key though: addMovies(movies: [{ "name": "name1" }] -- which throws an errorDimple

© 2022 - 2024 — McMap. All rights reserved.