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:
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:
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:
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.
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!