Extending GraphQL: Part 3 - Mutations

teeee

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!

Stay in the Loop

We will use the personal data you are sharing with us solely for the purpose of sending you our newsletter. See more here: Data Privacy.

GET IN TOUCH

Let us know how we can help you.

1