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.
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:
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.
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.
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.
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.
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.
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.
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
See the API Reference for:
- CreateModelMutation
- UpdateModelMutation,
- DeleteModelMutation
- and a more generic SerializerMutation.
Create mutation fields in schema
Last but not least we need to connect our mutations to the GraphQL schema!
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.
from apps.store import schema as store_schema
# ...
schema = graphene.Schema(
query=graphql_query([
# ...
store_schema.Query,
]),
mutation=graphql_mutation(
[
# ...
store_schema.Mutation,
]
),
)