Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove unnecessary fields before mutation in GraphQL

I've got a type called Article in my schema:

type Article {
  id: ID!
  updated: DateTime
  headline: String
  subline: String
}

For updates to it, there's a corresponding input type that is used by a updateArticle(id: ID!, article: ArticleInput!) mutation:

input ArticleInput {
  headline: String
  subline: String
}

The mutation itself looks like this:

mutation updateArticle($id: ID!, $article: ArticleInput!) {
  updateArticle(id: $id, article: $article) {
    id
    updated
    headline
    subline
  }
}

The article is always saved as a whole (not individual fields one by one) and so when I pass an article to that mutation that I've previously fetched, it throws errors like Unknown field. In field "updated", Unknown field. In field "__typename" and Unknown field. In field "id". These have the root cause, that those fields aren't defined on the input type.

This is correct behaviour according to the spec:

(…) This unordered map should not contain any entries with names not defined by a field of this input object type, otherwise an error should be thrown.

Now my question is what a good way to deal these kinds of scenarios is. Should I list all properties that are allowed on the input type in my app code?

If possible I'd like to avoid this and maybe have a utility function slice them off for me which knows about the input type. However, since the client doesn't know about the schema, this would have to happen on the server side. Thus, the unnecessary properties would be transferred there, which I suppose is the reason why they shouldn't be transferred in the first place.

Is there a better way than maintaining a list of properties?

I'm using apollo-client, react-apollo and graphql-server-express.

like image 631
amann Avatar asked Mar 06 '17 17:03

amann


People also ask

How do you deprecate fields in GraphQL?

The @deprecated directive allows you to tag the schema definition of a field or enum value as deprecated with an optional reason. When you use the @deprecated directive, GraphQL users can deprecate their use of the deprecated field or enum value.

How do you return nothing in GraphQL mutation?

According to this Github issue you cannot return nothing. You can define a return type which is nullable e.g. But I suggest you return the id of the deleted element, because if you want to work with a cached store you have to update the store when the delete mutation has ran successfully.

What is __ Typename in GraphQL?

The __typename field returns the object type's name as a String (e.g., Book or Author ). GraphQL clients use an object's __typename for many purposes, such as to determine which type was returned by a field that can return multiple types (i.e., a union or interface).

What is difference between query and mutation in GraphQL?

While we use queries to fetch data, we use mutations to modify server-side data. If queries are the GraphQL equivalent to GET calls in REST, then mutations represent the state-changing methods in REST (like DELETE , PUT , PATCH , etc).


1 Answers

You can use a fragment for the query, which includes all mutable fields of the data. That fragment can be used by a filter utility to remove all unwanted data before the mutation happens.

The gist is:

const ArticleMutableFragment = gql`
fragment ArticleMutable on Article {
  headline
  subline
  publishing {
    published
    time
  }
}
`

const ArticleFragment = gql`
fragment Article on Article {
  ...ArticleMutable
  id
  created
  updated
}
${ArticleMutableFragment}
`;

const query = gql`
query Article($id: ID!) {
  article(id: $id) {
    ...Article
  }
}
${ArticleFragment}
`;

const articleUpdateMutation = gql`
mutation updateArticle($id: ID!, $article: ArticleInput!) {
  updateArticle(id: $id, article: $article) {
    ...Article
  }
}
${ArticleFragment}
`;

...

import filterGraphQlFragment from 'graphql-filter-fragment';

...

graphql(articleUpdateMutation, {
  props: ({mutate}) => ({
    onArticleUpdate: (id, article) =>
      // Filter for properties the input type knows about
      mutate({variables: {id, article: filterGraphQlFragment(ArticleMutableFragment, article)}})
  })
})

...

The ArticleMutable fragment can now also be reused for creating new articles.

like image 195
amann Avatar answered Oct 19 '22 12:10

amann