# เจาะลึก Serializer ใน Django REST Framework: Validation, Nested และการแก้ปัญหา N+1 > คู่มือครบถ้วนสำหรับการใช้งาน DRF serializers: pipeline validation, nested serializers, การจัดการความสัมพันธ์ที่ซับซ้อน และเทคนิคการเพิ่มประสิทธิภาพเพื่อหลีกเลี่ยงปัญหา N+1 queries - Published: 2026-07-12 - Updated: 2026-07-12 - Author: SharpSkill - Tags: django, drf, serializers, api, python - Reading time: 5 min --- Serializer ใน Django REST Framework ทำหน้าที่สำคัญในการแปลง queryset และ model instance ให้เป็น JSON response—รวมถึงการ validate ข้อมูลขาเข้าก่อนที่จะบันทึกลง database แม้ว่าการใช้งาน serializer พื้นฐานจะดูเรียบง่าย แต่แอปพลิเคชัน production ต้องการความเชี่ยวชาญใน validation pipeline การจัดการความสัมพันธ์แบบ nested และการเพิ่มประสิทธิภาพ query > **กฎประสิทธิภาพ DRF Serializer** > > ทุก `SerializerMethodField` หรือ nested serializer ที่เข้าถึง object ที่เกี่ยวข้องโดยไม่มี `select_related`/`prefetch_related` จะทำให้เกิด database query เพิ่มเติม รายการ 100 object ที่มี 3 ความสัมพันธ์หมายถึง 301 queries แทนที่จะเป็น 4 ## ทำความเข้าใจ Validation Pipeline ของ DRF Serializer DRF serializer ดำเนินการ validation ตามลำดับที่เฉพาะเจาะจง: การ deserialize ระดับ field, validator ระดับ field จากนั้นจึงเป็น validation ระดับ object ผ่าน `validate()` Pipeline นี้กำหนดว่าจะเข้าแทรกการแปลงข้อมูลเมื่อใดและอย่างไร ลำดับ validation เริ่มต้นด้วย `to_internal_value()` ซึ่ง deserialize ชนิดข้อมูล primitive และรัน field validator หลังจากทุก field ผ่าน validation ทีละตัวแล้วเท่านั้น `validate()` จึงจะทำงานเพื่อตรวจสอบข้าม field ```python # serializers.py from rest_framework import serializers from django.utils import timezone from .models import Event class EventSerializer(serializers.ModelSerializer): start_date = serializers.DateTimeField() end_date = serializers.DateTimeField() class Meta: model = Event fields = ['id', 'title', 'start_date', 'end_date', 'location'] def validate_start_date(self, value): # Validation ระดับ field ทำงานก่อน if value < timezone.now(): raise serializers.ValidationError("วันที่เริ่มต้นต้องไม่อยู่ในอดีต") return value def validate(self, attrs): # Validation ระดับ object ทำงานหลังจากทุก field ถูก validate แล้ว start = attrs.get('start_date') end = attrs.get('end_date') if start and end and end <= start: raise serializers.ValidationError({ 'end_date': "วันที่สิ้นสุดต้องอยู่หลังวันที่เริ่มต้น" }) return attrs ``` การแยกนี้ช่วยให้ควบคุมได้ละเอียด: จับค่า field ที่ไม่ถูกต้องอย่างชัดเจนตั้งแต่เนิ่นๆ จากนั้นจึง validate กฎทางธุรกิจที่ครอบคลุมหลาย field ## Custom Validator และ Logic Validation ที่นำกลับมาใช้ซ้ำได้ DRF รองรับ validator สามรูปแบบ: method ระดับ field, class validator แบบ standalone และ argument `validators` Validator แบบ standalone ส่งเสริมการนำกลับมาใช้ซ้ำข้าม serializer และรักษาหลักการ single responsibility ```python # validators.py from rest_framework import serializers import re class SlugFormatValidator: """Validate รูปแบบ slug ที่ปลอดภัยสำหรับ URL""" def __init__(self, allow_unicode=False): self.allow_unicode = allow_unicode self.pattern = r'^[\w-]+$' if allow_unicode else r'^[a-z0-9-]+$' def __call__(self, value): if not re.match(self.pattern, value): raise serializers.ValidationError( "Slug ต้องมีเฉพาะตัวอักษรพิมพ์เล็ก ตัวเลข และเครื่องหมายขีดกลาง" ) class UniqueForUserValidator: """Validate ว่าค่าเป็นค่าเฉพาะสำหรับ user ปัจจุบัน""" requires_context = True def __init__(self, queryset, field_name): self.queryset = queryset self.field_name = field_name def __call__(self, value, serializer_field): request = serializer_field.context.get('request') if not request or not request.user.is_authenticated: return filter_kwargs = { self.field_name: value, 'user': request.user } # ยกเว้น instance ปัจจุบันเมื่อ update instance = serializer_field.parent.instance exists_query = self.queryset.filter(**filter_kwargs) if instance: exists_query = exists_query.exclude(pk=instance.pk) if exists_query.exists(): raise serializers.ValidationError( f"คุณมี item ที่มี {self.field_name} นี้อยู่แล้ว" ) ``` Validator ที่ต้องการ context ใช้ flag `requires_context = True` และรับ `serializer_field` เป็น argument ที่สอง สิ่งนี้ให้การเข้าถึง request, instance และ serializer แม่ทั้งหมด ```python # serializers.py from .validators import SlugFormatValidator, UniqueForUserValidator from .models import Article class ArticleSerializer(serializers.ModelSerializer): slug = serializers.CharField( max_length=100, validators=[ SlugFormatValidator(allow_unicode=False), UniqueForUserValidator( queryset=Article.objects.all(), field_name='slug' ) ] ) class Meta: model = Article fields = ['id', 'title', 'slug', 'content', 'published'] ``` ## การจัดการ Nested Serializer สำหรับความสัมพันธ์ที่ซับซ้อน Nested serializer จำลองความสัมพันธ์ database ใน API response ความท้าทายที่แท้จริงอยู่ที่การจัดการ operation create และ update—โดยเฉพาะเมื่อทำงานกับความสัมพันธ์ many-to-many หรือ reverse foreign key ```python # models.py from django.db import models class Author(models.Model): name = models.CharField(max_length=200) email = models.EmailField(unique=True) class Tag(models.Model): name = models.CharField(max_length=50, unique=True) slug = models.SlugField(unique=True) class Book(models.Model): title = models.CharField(max_length=300) isbn = models.CharField(max_length=13, unique=True) author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books') tags = models.ManyToManyField(Tag, related_name='books') published_date = models.DateField() class Chapter(models.Model): book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='chapters') title = models.CharField(max_length=200) order = models.PositiveIntegerField() content = models.TextField() ``` Nested serializer แบบอ่านอย่างเดียวสามารถรวมได้โดยตรง แต่ nested ที่เขียนได้ต้องมีการ override `create()` และ `update()` อย่างชัดเจน ```python # serializers.py from rest_framework import serializers from django.db import transaction from .models import Author, Tag, Book, Chapter class ChapterSerializer(serializers.ModelSerializer): class Meta: model = Chapter fields = ['id', 'title', 'order', 'content'] class TagSerializer(serializers.ModelSerializer): class Meta: model = Tag fields = ['id', 'name', 'slug'] class AuthorSerializer(serializers.ModelSerializer): class Meta: model = Author fields = ['id', 'name', 'email'] class BookSerializer(serializers.ModelSerializer): author = AuthorSerializer(read_only=True) author_id = serializers.PrimaryKeyRelatedField( queryset=Author.objects.all(), source='author', write_only=True ) tags = TagSerializer(many=True, read_only=True) tag_ids = serializers.PrimaryKeyRelatedField( queryset=Tag.objects.all(), source='tags', many=True, write_only=True ) chapters = ChapterSerializer(many=True) class Meta: model = Book fields = [ 'id', 'title', 'isbn', 'author', 'author_id', 'tags', 'tag_ids', 'chapters', 'published_date' ] @transaction.atomic def create(self, validated_data): chapters_data = validated_data.pop('chapters', []) tags = validated_data.pop('tags', []) book = Book.objects.create(**validated_data) book.tags.set(tags) for chapter_data in chapters_data: Chapter.objects.create(book=book, **chapter_data) return book @transaction.atomic def update(self, instance, validated_data): chapters_data = validated_data.pop('chapters', None) tags = validated_data.pop('tags', None) # Update field ธรรมดา for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() # Update ความสัมพันธ์ many-to-many ถ้ามีการระบุ if tags is not None: instance.tags.set(tags) # จัดการ update nested chapters if chapters_data is not None: # กลยุทธ์: ลบที่มีอยู่และสร้างใหม่ instance.chapters.all().delete() for chapter_data in chapters_data: Chapter.objects.create(book=instance, **chapter_data) return instance ``` รูปแบบนี้แยก operation อ่านและเขียน: field nested ให้ output ที่จัดรูปแบบสวยงาม ขณะที่ field `*_id` รับ primary key สำหรับ input Wrapper `@transaction.atomic` รับประกันความสอดคล้องของข้อมูลเมื่อ operation หลายตัวล้มเหลวบางส่วน ## เทคนิคการเพิ่มประสิทธิภาพเพื่อหลีกเลี่ยงปัญหา N+1 Query ปัญหาประสิทธิภาพที่พบบ่อยที่สุดใน DRF มาจาก N+1 query ที่สร้างโดย nested serializer และ method field Endpoint ที่คืนค่า 50 หนังสือพร้อม author, tag และ chapter จะรัน query หลายร้อยครั้งโดยไม่มีการเพิ่มประสิทธิภาพที่เหมาะสม ```python # views.py from rest_framework import viewsets from django.db.models import Prefetch from .models import Book, Chapter from .serializers import BookSerializer class BookViewSet(viewsets.ModelViewSet): serializer_class = BookSerializer def get_queryset(self): return Book.objects.select_related( 'author' ).prefetch_related( 'tags', Prefetch( 'chapters', queryset=Chapter.objects.order_by('order') ) ) ``` การเลือกระหว่าง `select_related` และ `prefetch_related` เป็นไปตามกฎง่ายๆ: `select_related` สำหรับความสัมพันธ์ single-object (ForeignKey, OneToOne) ใช้ SQL JOIN ขณะที่ `prefetch_related` สำหรับความสัมพันธ์ multi-object (ManyToMany, reverse ForeignKey) ใช้ query แยกต่างหาก ### การจัดการ SerializerMethodField อย่างมีประสิทธิภาพ `SerializerMethodField` มักทำให้เกิดปัญหา N+1 เพราะ query ถูกรันต่อ instance การแก้ไขเกี่ยวข้องกับการ annotate queryset หรือการใช้ prefetch อย่างระมัดระวัง ```python # serializers.py class BookListSerializer(serializers.ModelSerializer): author_name = serializers.CharField(source='author.name', read_only=True) chapter_count = serializers.IntegerField(read_only=True) tag_names = serializers.SerializerMethodField() class Meta: model = Book fields = ['id', 'title', 'author_name', 'chapter_count', 'tag_names'] def get_tag_names(self, obj): # ปลอดภัยถ้า tags ถูก prefetch แล้ว return [tag.name for tag in obj.tags.all()] # views.py from django.db.models import Count class BookListViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = BookListSerializer def get_queryset(self): return Book.objects.select_related( 'author' ).prefetch_related( 'tags' ).annotate( chapter_count=Count('chapters') ) ``` การ annotate `Count` นับ chapter ใน query เดียวแทนที่จะ fetch ความสัมพันธ์และนับใน Python การเข้าถึง `author.name` ผ่าน source ทำงานได้เพราะ `select_related('author')` โหลดข้อมูล author แล้ว ### Custom Prefetch สำหรับความสัมพันธ์ที่กรองแล้ว Object `Prefetch` อนุญาตให้ custom query สำหรับความสัมพันธ์ที่ถูก prefetch ช่วยให้กรองและเรียงลำดับได้โดยไม่ต้องมี query เพิ่มเติม ```python # views.py from django.db.models import Prefetch from .models import Book, Chapter, Review class BookDetailViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = BookDetailSerializer def get_queryset(self): return Book.objects.select_related( 'author' ).prefetch_related( 'tags', Prefetch( 'chapters', queryset=Chapter.objects.order_by('order').only( 'id', 'title', 'order', 'book_id' ) ), Prefetch( 'reviews', queryset=Review.objects.filter( is_approved=True ).select_related('user').order_by('-created_at')[:5], to_attr='recent_reviews' ) ) ``` Attribute `to_attr` เก็บผลลัพธ์ prefetch ใน attribute ที่กำหนดเอง (`recent_reviews`) แทนที่จะแทนที่ relation manager เริ่มต้น สิ่งนี้มีประโยชน์เมื่อต้องการเข้าถึงทั้ง subset ที่กรองแล้วและความสัมพันธ์แบบเต็ม ## การควบคุม Serialization ขั้นสูงด้วย to_representation Method `to_representation()` ให้การควบคุมอย่างเต็มที่ต่อ output ที่ถูก serialize มันทำงานหลังจาก field serialization ทั้งหมด ช่วยให้แปลงตาม context หรือข้อมูล instance ได้ ```python # serializers.py class BookSerializer(serializers.ModelSerializer): chapters = ChapterSerializer(many=True, read_only=True) class Meta: model = Book fields = ['id', 'title', 'isbn', 'author', 'chapters', 'published_date'] def to_representation(self, instance): data = super().to_representation(instance) request = self.context.get('request') # ลบ field ที่ sensitive สำหรับ user ที่ไม่ใช่ staff if request and not request.user.is_staff: data.pop('isbn', None) # เพิ่ม field แบบมีเงื่อนไข if instance.published_date: from django.utils import timezone data['is_new_release'] = ( timezone.now().date() - instance.published_date ).days < 30 # ปรับโครงสร้าง output nested if 'chapters' in data: data['chapter_count'] = len(data['chapters']) data['chapters'] = data['chapters'][:3] # แค่ 3 ตัวแรกใน response return data ``` ## การ Debug Query Serializer Package `django-debug-toolbar` และ `django-silk` มีคุณค่ามากสำหรับการระบุปัญหา query สำหรับ logging แบบ programmatic wrapper queryset ธรรมดาก็เพียงพอ ```python # utils.py import logging from django.db import connection, reset_queries from django.conf import settings from functools import wraps logger = logging.getLogger(__name__) def log_queries(func): @wraps(func) def wrapper(*args, **kwargs): if not settings.DEBUG: return func(*args, **kwargs) reset_queries() result = func(*args, **kwargs) queries = connection.queries logger.debug( f"{func.__name__} executed {len(queries)} queries" ) for query in queries: logger.debug(f" [{query['time']}s] {query['sql'][:100]}") return result return wrapper # views.py class BookViewSet(viewsets.ModelViewSet): @log_queries def list(self, request, *args, **kwargs): return super().list(request, *args, **kwargs) ``` ## สรุป การเชี่ยวชาญ DRF serializer ต้องการความเข้าใจใน validation pipeline, รูปแบบ nested relationship และกลยุทธ์การเพิ่มประสิทธิภาพ query องค์ประกอบหลักที่ต้องจดจำ ได้แก่: ใช้ validation ระดับ field สำหรับการตรวจสอบรายบุคคลและ `validate()` สำหรับกฎข้าม field, แยก field read-only และ write-only สำหรับ nested relationship ที่สะอาด, ใช้ `select_related` และ `prefetch_related` เสมอใน queryset ของ view และใช้ประโยชน์จาก `to_representation()` สำหรับ output แบบมีเงื่อนไข การนำรูปแบบเหล่านี้ไปใช้รับประกันว่า API ไม่เพียงแต่ทำงานถูกต้องเท่านั้น แต่ยังทำงานได้ดีภายใต้ภาระงาน production จริง Serializer ที่มีประสิทธิภาพลดเวลาตอบสนองและภาระ server ได้อย่างมาก --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/django/django-rest-framework-serializers-deep-dive