Skip to main content

How to add a new mutation to back-end API?

Django model based mutation

Let's imagine you have a store module, and you want to include a new database model that allows your users to create, update and delete products.

info

If you're also interested in front-end part of this guide check out Form with mutation guide.

Create Django model

You may already have your own model, but the examples in this guide are built upon the following Django model:

packages/backend/apps/store/models.py
import hashid_field
from django.db import models


class Product(models.Model):
id = hashid_field.HashidAutoField(primary_key=True)
name = models.CharField(max_length=255)

Create Django Rest Framework serializer

In ShipFast, GraphQL mutations use DRF Serializers to validate data and run business logic.

danger

Even if you use DRF ModelSerializer, all fields are treated as write-only. This is because mutations return GraphQL type objects, which are defined independently from serializers.

packages/backend/apps/store/serializers.py
from hashid_field import rest as hidrest
from rest_framework import serializers


class ProductSerializer(serializers.ModelSerializer):
id = hidrest.HashidSerializerCharField(source_field="store.Product.id", read_only=True)

class Meta:
model = models.Product
fields = ('id', 'name',)

This is essentially a regular DRF serializer. You can perform all side effects in either the update or create method.

Moreover, you can connect this serializer to more than one mutation, similar to how you would do it with DRF views.

Define GraphQL type and connection

You can use Graphene Django to automatically generate GraphQL schema for Django models. Your future mutation will reference those types to build both input and output schema.

packages/backend/apps/store/schema.py
from graphene import relay
from graphene_django import DjangoObjectType

from . import models

class ProductType(DjangoObjectType):
class Meta:
model = models.Product
interfaces = (relay.Node,)
fields = "__all__"


class ProductConnection(graphene.Connection):
class Meta:
node = ProductType
  • DjangoObjectType – automatically transforms a Django Model into a ObjectType for you
  • Connection – A connection is a vitaminized version of a List that provides ways of slicing and paginating through it.

Define mutations

CreateModelMutation

The CreateModelMutation class provides a generic implementation of a mutation that creates a new object of the specified model type. It handles the creation of the object and validation of the input data, and returns the newly created object as part of the mutation response. Please ensure that you also define an edge_class in Meta, or else your schema will not include the productEdge field. This field could be useful to your frontend, as it can be used to easily push newly created data into an existing collection.

packages/backend/apps/store/schema.py
from common.graphql import mutations
from . import models, serializers


class CreateProductMutation(mutations.CreateModelMutation):
class Meta:
serializer_class = serializers.ProductSerializer
edge_class = ProductConnection.Edge

UpdateModelMutation

The UpdateModelMutation class provides a generic implementation of a mutation that updates an object of the specified model type. It handles the update of the object and validation of the input data, and returns the updated object as part of the mutation response. Meta class fields are the same as above.

packages/backend/apps/store/schema.py
from common.graphql import mutations
from . import models, serializers


class CreateProductMutation(mutations.CreateModelMutation):
class Meta:
serializer_class = serializers.ProductSerializer
edge_class = ProductConnection.Edge


class UpdateProductMutation(mutations.UpdateModelMutation):
class Meta:
serializer_class = serializers.ProductSerializer
edge_class = ProductConnection.Edge

DeleteModelMutation

The DeleteModelMutation class is a subclass of ClientIDMutation. It serves as a blueprint for mutations that delete objects from the database.

The Meta class attribute is used to specify the model that this mutation will operate on. In this case, it is set to models.Product.

To delete the object, developers can simply pass the id of the Product object they wish to delete as a parameter to the mutation. The mutation will delete the object from the database and return a payload containing the ID of the deleted object.

packages/backend/apps/store/schema.py
from common.graphql import mutations
from . import models, serializers


class CreateProductMutation(mutations.CreateModelMutation):
class Meta:
serializer_class = serializers.ProductSerializer
edge_class = ProductConnection.Edge


class UpdateProductMutation(mutations.UpdateModelMutation):
class Meta:
serializer_class = serializers.ProductSerializer
edge_class = ProductConnection.Edge


class DeleteProductMutation(mutations.DeleteModelMutation):
class Meta:
model = models.Product
tip

Create mutation fields in schema

Last but not least we need to connect our mutations to the GraphQL schema!

packages/backend/apps/store/schema.py
import graphene
from common.graphql import mutations
from . import models, serializers


class CreateProductMutation(mutations.CreateModelMutation):
class Meta:
serializer_class = serializers.ProductSerializer
edge_class = ProductConnection.Edge


class UpdateProductMutation(mutations.UpdateModelMutation):
class Meta:
serializer_class = serializers.ProductSerializer
edge_class = ProductConnection.Edge


class DeleteProductMutation(mutations.DeleteModelMutation):
class Meta:
model = models.Product


class Mutation(graphene.ObjectType):
create_product = CreateProductMutation.Field()
update_product = UpdateProductMutation.Field()
delete_product = DeleteProductMutation.Field()

It is possible that the referenced file already contains a Mutation object. If that is the case, you can add your mutation as another field beside the existing ones.

However, if it does not exist yet, it is important to add it to the root of the schema. Think of this similarly to urls.py used by Django apps. It is located in config python package.

packages/backend/config/schema.py
from apps.store import schema as store_schema

# ...

schema = graphene.Schema(
query=graphql_query([
# ...
store_schema.Query,
]),
mutation=graphql_mutation(
[
# ...
store_schema.Mutation,
]
),
)