Parse-server: GraphQL: Using generic Mutation can lead to unwanted result

Created on 15 Aug 2019  路  5Comments  路  Source: parse-community/parse-server

Issue Description

Using a generic Mutation can lead to unwanted result, more specifically on data types like: Array/Relation.

Steps to reproduce

Create a new Object (with new field) and an array of Pointers

mutation {
  objects {
    create(
      className: "SomeClass"
      fields: {
        relation: [
          { __type: "Pointer", className: "Country", objectId: "GFFCf5dxKW" }
        ]
      }
    ) {
      objectId
    }
  }
}

Expected Results

relation field must be a Relation

Actual Outcome

Capture d鈥檈虂cran 2019-08-15 a虁 23 32 45

The data type is interpreted as an Array

Environment Setup

  • Server

    • parse-server version (Be specific! Don't say 'latest'.) : master
    • Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): localhost
  • Database

    • MongoDB

Suggestion

I think that generic Mutations isn't suitable in real GraphQL app but it's useful for testing the current GraphQL feature. The generic Mutation currently seems to be a black box and can be confusing for new developers who are not familiar with SDKs (Polygon, ACL, Pointer, etc...)

In fact, I think that in most applications that will use the Parse GraphQL as API, the SDKs will be used little or not at all to interact with the GraphQL because it is not comfortable and really logical to combine a SDK and a API GraphQL in terms of developer experience.

We should teach developers to create first a schema with a well designed createClass/createSchema Mutation and then use a specific Mutation

What do you think about this @davimacedo @omairvaiyani @douglasmuraoka ?

All 5 comments

The sintax for creating a relationField through the generic method is the same of the REST API. So it whould be:

mutation {
  objects {
    create(
      className: "SomeClass"
      fields: {
        relation: {
          __op: "Batch",
          ops: [{ __op: "AddRelation", objects: [{ __type: "Pointer", className: "Country", objectId: "GFFCf5dxKW" }]}]
      }
    ) {
      objectId
    }
  }
}

See test case here: https://github.com/parse-community/parse-server/blob/master/spec/ParseGraphQLServer.spec.js#L5119

I know it is not intuitive and need docs. The developers can always opt for the custom operations though. The idea behind the generic operations is to keep the schemaless feature in Parse GraphQL API but we can discuss it further and perhaps remove them.

I was thinking little bit more here. Maybe an option could be just remove all generic operations and build new operations to manipulate the schema?

Yes not intuitive 馃槈
I'm not sure it's a solution to add more doc on a GraphQL API, the main goal of GraphQL is to avoid documentation and dive directly in Mutations and Queries.

I was thinking little bit more here. Maybe an option could be just remove all generic operations and build new operations to manipulate the schema?

It's totally what i suggest, a developer could use the full potential of Parse GraphQL Server without reading single line of external documentation

A example of DX:

  • Developer see mutation: createClass ou createSchema
  • He can use enum to choose data types: Relation, Polygon, Pointer during class creation
  • Schema is now created
  • Now he can create the new object with a full typed help (particularly useful for special data types, ACL (we need to type this 馃槈 ), Polygon, etc...)
  • The developer can now use the full power of Parse Server without reading single line of an external doc.

5863

Type Proposal

type Mutation {
    createClass(input: CreateClassInput): Boolean
    updateClass(input: UpdateClassInput): Boolean
    createSchema(input: [CreateClassInput]): Boolean
    updateSchema(input: [UpdateClassInput]): Boolean
}

input AddArrayInput {
    name: String!
}

input AddRelationInput {
    name: String!
    targetClass: String!
}

input AddNumberInput {
    name: String!
}

input AddBooleanInput {
    name: String!
}

input AddFileInput {
    name: String!
}

input AddPointerInput {
    name: String!
    targetClass: String!
}

input AddDateInput {
    name: String!
}

input AddGeoPointInput {
    name: String!
}

input AddObjectInput {
    name: String!
}

input AddStringInput {
    name: String!
}

input DeleteField {
    name: String!
}

input SchemaClassInput {
    addArrays: [AddArrayInput!]
    addStrings: [AddRelationInput!]
    addNumbers: [AddRelationInput!]
    addDates: [AddRelationInput!]
    addGeoPoints: [AddRelationInput!]
    addFiles: [AddRelationInput!]
    addRelations: [AddRelationInput!]
    addObjects: [AddObjectsInput!]
    addPointers: [AddPointersInput!]
    addPolygons: [AddPolygonsInput!]
    addBooleans: [AddBooleansInput!]
}

input CreateClassInput {
    className: String!
    schema: SchemaClassInput
}

input UpdateClassInput {
    className: ClassNameEnum!
    schema: SchemaClassInput
}

Mutation example:

mutation {
  createClass (input: {
    className: "SomeClass"
    schema: {
      addArrays: [
        {name: "anArray"}
      ]
      addRelations: [
        {name: "users", targetClass: "User"}
        {name: "products", targetClass: "Product"}
      ]
    }
  })
}
Was this page helpful?
0 / 5 - 0 ratings