Django REST Framework Validation with Serializers

Last updated 1 month, 2 weeks ago | 134 views 75     5

Tags:- Python Django DRF

Validating data is a critical part of building any API. In Django REST Framework (DRF), serializers provide built-in and customizable validation mechanisms that ensure your input data is clean, consistent, and secure before hitting the database.

In this article, you'll learn:

  • Why validation is important

  • DRF’s validation flow

  • validate_<field>() for field-level validation

  • validate() for object-level validation

  • Custom error messages

  • Real-world examples

  • Best practices and common mistakes


Why is Validation Important?

Validation helps:

  • Prevent invalid or malicious data from being saved

  • Enforce business logic

  • Improve API reliability and user experience

For example, a user shouldn’t be allowed to set a future date for a past event or enter the same value for "username" and "email".


DRF Validation Flow Overview

When you call .is_valid() on a serializer:

  1. Each field is validated individually (validate_<field>())

  2. The entire object is validated (validate())

  3. If everything is valid, .save() creates or updates the model


Field-Level Validation with validate_<field>()

Use this method to add logic for a specific field:

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['title', 'author', 'published']

    def validate_title(self, value):
        if "test" in value.lower():
            raise serializers.ValidationError("Title cannot contain 'test'.")
        return value
  • value is the incoming data for the field.

  • You must return value if it’s valid.


Object-Level Validation with validate(self, data)

Use this to validate multiple fields together:

def validate(self, data):
    if data['title'] == data['author']:
        raise serializers.ValidationError("Title and author cannot be the same.")
    return data

This is ideal when validation depends on a combination of fields.


✍️ Custom Error Messages

You can customize error messages in two ways:

1. Inside the serializer field definition:

title = serializers.CharField(
    max_length=100,
    error_messages={'blank': 'Title is required.', 'max_length': 'Title is too long.'}
)

2. In the validation methods:

raise serializers.ValidationError("Custom error message here.")

Example: User Registration Serializer

class RegisterSerializer(serializers.Serializer):
    username = serializers.CharField()
    email = serializers.EmailField()
    password = serializers.CharField(write_only=True)
    password2 = serializers.CharField(write_only=True)

    def validate_username(self, value):
        if User.objects.filter(username=value).exists():
            raise serializers.ValidationError("Username is already taken.")
        return value

    def validate(self, data):
        if data['password'] != data['password2']:
            raise serializers.ValidationError("Passwords do not match.")
        return data

Common Pitfalls

Problem Solution
Returning nothing from validate() Always return data
Not using write_only=True for passwords Use write_only=True to avoid exposing sensitive fields
Using clean() from Django forms in DRF Use validate() instead
Putting object-level logic in field-level validators Use validate() for multi-field checks

✅ Best Practices

  • ✅ Use validate_<field>() for individual field checks

  • ✅ Use validate() for logic that spans fields

  • ✅ Return cleaned value or data from each validator

  • ✅ Keep error messages user-friendly

  • ✅ Reuse validation functions to avoid duplication


Full Example: Blog Post Validator

from rest_framework import serializers
from .models import BlogPost

class BlogPostSerializer(serializers.ModelSerializer):
    class Meta:
        model = BlogPost
        fields = ['title', 'content', 'author']

    def validate_title(self, value):
        if len(value) < 10:
            raise serializers.ValidationError("Title must be at least 10 characters long.")
        return value

    def validate(self, data):
        if 'django' in data['content'].lower() and not data['title'].lower().startswith('django'):
            raise serializers.ValidationError("Posts about Django must start the title with 'Django'.")
        return data

Final Thoughts

Proper validation is essential in building robust APIs. DRF's ModelSerializer makes it easy to enforce field-level and object-level validation using validate_<field>() and validate() methods.

With well-crafted validation logic, your API will be secure, user-friendly, and resilient against bad input.