Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Extending GraphQL: Part 3 - Mutations

Parent Feed: 

The graphql_core module bundled with the graphql module automatically exposes types and fields to traverse Drupal’s entity system. However, since beta 1 it does not do the same for mutations anymore. The fact that it is not possible to write or update data using GraphQL caused much confusion. I want to shed light on this topic and explain the way mutations are intended to work.

Before releasing the first beta version of the GraphQL module for Drupal, we removed a feature that automatically added input types and mutation fields for all content entities in the GraphQL schema. This may seem to be counter-intuitive, but there were ample reasons.

Why automatic mutations were removed

While GraphQL allows the client to freely shape query and response, mutations (create, update or delete operations) are by design atomic. A mutation is a root level field that is supposed to accept all necessary information as arguments and return a queryable data object, representing the state after the operation. Since GraphQL is strictly typed, this means that there is one mutation field and one distinct input type for every entity bundle. Also, because Drupal entities tend to have a lot of fields and properties, this resulted in very intricate and hard to use mutations, increasing the schema size, even though  90% of them were never used. 

On top of that, some entity structures added additional complexities: For example, just trying to create an article with a title and a body value while the comment module is enabled results in a constraint violation, as the comment field requires an empty list of comments at the least. 

These circumstances led to a technically correct solution that unfortunately burdened the client with too much knowledge about Drupal internals and was therefore not usable in practice. It became apparent that this had to break and change in the future. Now, because we removed it, the rest of the GraphQL API can become stable. The code is still available on Github for reference and backwards compatibility, but there are no plans to maintain this further.

How to use mutations

So there is no way to use mutations out of the box. You have to write code to add a mutation, but that doesn’t mean it’s complicated. Let’s walk through a simple example. All code is available in the examples repository.

First, you have to add an input type that defines the shape of the data you want your entity mutation to accept:

<?php

namespace Drupal\graphql_examples\Plugin\GraphQL\InputTypes;

use Drupal\graphql\Plugin\GraphQL\InputTypes\InputTypePluginBase;

/**
 * The input type for article mutations.
 *
 * @GraphQLInputType(
 *   id = "article_input",
 *   name = "ArticleInput",
 *   fields = {
 *     "title" = "String",
 *     "body" = {
 *        "type" = "String",
 *        "nullable" = "TRUE"
 *     }
 *   }
 * )
 */
class ArticleInput extends InputTypePluginBase {

}

This plugin defines a new input type that consists of a “title” and a “body” field.

The first mutation plugin is the “create” operation. It extends the CreateEntityBase class and implements only one method, which will map the properties of our input type (above) to the target entity type and bundle, as defined in the annotation.

<?php

namespace Drupal\graphql_examples\Plugin\GraphQL\Mutations;

use Drupal\graphql\GraphQL\Type\InputObjectType;
use Drupal\graphql_core\Plugin\GraphQL\Mutations\Entity\CreateEntityBase;
use Youshido\GraphQL\Execution\ResolveInfo;

/**
 * Simple mutation for creating a new article node.
 *
 * @GraphQLMutation(
 *   id = "create_article",
 *   entity_type = "node",
 *   entity_bundle = "article",
 *   secure = true,
 *   name = "createArticle",
 *   type = "EntityCrudOutput",
 *   arguments = {
 *      "input" = "ArticleInput"
 *   }
 * )
 */
class CreateArticle extends CreateEntityBase {

  /**
   * {@inheritdoc}
   */
  protected function extractEntityInput(array $inputArgs, InputObjectType $inputType, ResolveInfo $info) {
    return [
      'title' => $inputArgs['title'],
      'body' => $inputArgs['body'],
    ];
  }

}

The base class handles the rest. Now you already can issue a mutation using the GraphQL Explorer:

Creating an article node with GraphQL.

The mutation will return an object of type EntityCrudOutput that already contains any errors or constraint violations, as well as - in case the operation was successful - the newly created entity.

If you try to create an article with an empty title, typed data constraints will kick in, and the mutation will fail accordingly:

Typed data constraint violations in GraphQL.

The update mutation looks almost the same. It just requires an additional argument id that contains the id of the entity to update and extends a different base class.

<?php

namespace Drupal\graphql_examples\Plugin\GraphQL\Mutations;

use Drupal\graphql\GraphQL\Type\InputObjectType;
use Drupal\graphql_core\Plugin\GraphQL\Mutations\Entity\UpdateEntityBase;
use Youshido\GraphQL\Execution\ResolveInfo;

/**
 * Simple mutation for updating an existing article node.
 *
 * @GraphQLMutation(
 *   id = "update_article",
 *   entity_type = "node",
 *   entity_bundle = "article",
 *   secure = true,
 *   name = "updateArticle",
 *   type = "EntityCrudOutput",
 *   arguments = {
 *      "id" = "String",
 *      "input" = "ArticleInput"
 *   }
 * )
 */
class UpdateArticle extends UpdateEntityBase {

  /**
   * {@inheritdoc}
   */
  protected function extractEntityInput(array $inputArgs, InputObjectType $inputType, ResolveInfo $info) {
    return array_filter([
      'title' => $inputArgs['title'],
      'body' => $inputArgs['body'],
    ]);
  }

}

Now you should be able to alter any articles:

Updating an existing article with GraphQL.

And the delete mutation is even simpler.

<?php

namespace Drupal\graphql_examples\Plugin\GraphQL\Mutations;

use Drupal\graphql_core\Plugin\GraphQL\Mutations\Entity\DeleteEntityBase;

/**
 * Simple mutation for deleting an article node.
 *
 * @GraphQLMutation(
 *   id = "delete_article",
 *   entity_type = "node",
 *   entity_bundle = "article",
 *   secure = true,
 *   name = "deleteArticle",
 *   type = "EntityCrudOutput",
 *   arguments = {
 *      "id" = "String"
 *   }
 * )
 */
class DeleteArticle extends DeleteEntityBase {

}

This will delete the entity, but it’s still available in the response object, for rendering notifications or for subsequent queries.

Deleting an article using GraphQL mutations.

That’s a wrap. With some trivial code, we can implement a full CRUD interface for an entity type. If you need multiple entity types, you could use derivers and services to make it more DRY.

Plans

This way we can create entity mutations that precisely fit the needs of our current project. It requires a little boilerplate code and might not be the most convenient thing to do, but it’s not terrible and works for now.

That doesn’t mean we are not planning to improve. Currently, the rules module is our best hope for providing zero-code, site-building driven mutations. The combination would be tremendously powerful.

If you want out-of-the-box mutations in GraphQL, go and help with #d8rules!

Author: 
Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web