graphql, how to design input type when there are “add” and “update” mutation?

喜夏-厌秋 提交于 2021-02-07 05:18:37

问题


Here are my requirements:

  1. "add" mutation, every field(or called scalar) of BookInput input type should have additional type modifiers "!" to validate the non-null value. Which means when I add a book, the argument must have title and author field, like {title: "angular", author: "novaline"}

  2. "update" mutation, I want to update a part of fields of the book, don't want to update whole book(MongoDB document, And, I don't want front-end to pass graphql server a whole big book mutation argument for saving bandwidth). Which means the book argument can be {title: "angular"} or {title: "angular", author: "novaline"}.

Here are my type definitions:

const typeDefs = `
  input BookInput {
    title: String!
    author: String!
  }

  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type Query {
    books: [Book!]!
  }

  type Mutation{
    add(book: BookInput!): Book
    update(id: String!, book: BookInput!): Book
  }
`;

For now, "add" mutation works fine. But "update" mutation cannot pass the non-null check if I pass {title: "angular"} argument

Here is a mutation which does not pass the non-null check, lack of "author" field for BookInput input type.

mutation {
  update(id: "1", book: {title: "angular"}) {
    id
    title
    author
  }
}

So, graphql will give me an error:

{
  "errors": [
    {
      "message": "Field BookInput.author of required type String! was not provided.",
      "locations": [
        {
          "line": 2,
          "column": 24
        }
      ]
    }
  ]
}

How do I design the BookInput input type? Don't want to define addBookInput and updateBookInput. It's duplicated.


回答1:


A very common pattern is to have separate input types for each mutation. You may also want to create one mutation query per operation. Perhaps something like this:

const typeDefs = `
  input AddBookInput {
    title: String!
    author: String!
  }

  input UpdateBookInput {
    # NOTE: all fields are optional for the update input 
    title: String
    author: String
  }

  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type Query {
    books: [Book!]!
  }

  type Mutation{
    addBook(input: AddBookInput!): Book
    updateBook(id: String!, input: UpdateBookInput!): Book
  }
`;

Some people also like to include the update ID as part of the update input:

const typeDefs = `
  input AddBookInput {
    title: String!
    author: String!
  }

  input UpdateBookInput {
    # NOTE: all fields, except the 'id' (the selector), are optional for the update input 
    id: String!
    title: String
    author: String
  }

  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type Query {
    books: [Book!]!
  }

  type Mutation{
    addBook(input: AddBookInput!): Book
    updateBook(input: UpdateBookInput!): Book
  }
`;

Finally, you may want to use a 'payload' type for the return type - for added flexibility (gives you more wiggle room to change the return type later without breaking your API):

const typeDefs = `
  input AddBookInput {
    title: String!
    author: String!
  }

  input UpdateBookInput {
    # NOTE: all fields, except the 'id' (the selector), are optional for the update input 
    id: String!
    title: String
    author: String
  }

  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type AddBookPayload {
    book: Book!
  }

  type UpdateBookPayload {
    book: Book!
  }

  type Query {
    books: [Book!]!
  }

  type Mutation{
    addBook(input: AddBookInput!): AddBookPayload!
    updateBook(input: UpdateBookInput!): UpdateBookPayload!
  }
`;

Hope this helps!




回答2:


Here is my solution, I write a helper function to generate "create" input type and "update" input type.

const { parse } = require('graphql');

/**
 * schema definition helper function - dynamic generate graphql input type
 *
 * @author https://github.com/mrdulin
 * @param {string} baseSchema
 * @param {object} options
 * @returns {string}
 */
function generateInputType(baseSchema, options) {
  const inputTypeNames = Object.keys(options);
  const schema = inputTypeNames
    .map(inputTypeName => {
      const { validator } = options[inputTypeName];
      const validatorSchema = Object.keys(validator)
        .map(field => `${field}: ${validator[field]}\n`)
        .join(' ');

      return `
      input ${inputTypeName} {
        ${baseSchema}
        ${validatorSchema}
      }
    `;
    })
    .join(' ')
    .replace(/^\s*$(?:\r\n?|\n)/gm, '');

  parse(schema);
  return schema;
}

schema.js:

${generateInputType(
  `
  campaignTemplateNme: String
`,
  {
    CreateCampaignTemplateInput: {
      validator: {
        channel: 'ChannelUnionInput!',
        campaignTemplateSharedLocationIds: '[ID]!',
        campaignTemplateEditableFields: '[String]!',
        organizationId: 'ID!',
      },
    },
    UpdateCampaignTemplateInput: {
      validator: {
        channel: 'ChannelUnionInput',
        campaignTemplateSharedLocationIds: '[ID]',
        campaignTemplateEditableFields: '[String]',
        organizationId: 'ID',
      },
    },
  },
)}


来源:https://stackoverflow.com/questions/51517363/graphql-how-to-design-input-type-when-there-are-add-and-update-mutation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!