Django 5: สร้าง REST API ด้วย Django REST Framework
คู่มือฉบับสมบูรณ์สำหรับการสร้าง REST API ระดับมืออาชีพด้วย Django 5 และ DRF ครอบคลุม Serializers, ViewSets, การยืนยันตัวตน JWT และแนวทางปฏิบัติที่ดีที่สุด

Django REST Framework (DRF) ยังคงเป็นมาตรฐานสูงสุดสำหรับการสร้าง REST API ด้วย Python เมื่อใช้ร่วมกับ Django 5 แล้ว framework นี้มอบประสบการณ์การพัฒนาที่ยอดเยี่ยมผ่าน serializers ที่ทรงพลัง, ViewSets อัตโนมัติ และระบบยืนยันตัวตนที่ยืดหยุ่น คู่มือนี้ครอบคลุมการสร้าง API ระดับมืออาชีพทั้งหมด ตั้งแต่การติดตั้งจนถึงการทดสอบ
Django REST Framework 3.15 รองรับ Django 5 อย่างเต็มรูปแบบ ปรับปรุงประสิทธิภาพอย่างมีนัยสำคัญ และทำงานร่วมกับชนิดข้อมูล native ของ Python ได้ดียิ่งขึ้น การใช้งานร่วมกันนี้ยังคงเป็นตัวเลือกอันดับแรกสำหรับ API Python ในสภาพแวดล้อม production
Project Installation and Configuration
การตั้งค่าโปรเจกต์ Django กับ DRF ต้องการขั้นตอนการกำหนดค่าบางประการ การใช้ virtual environment และโครงสร้างโปรเจกต์ที่ชัดเจนจะช่วยให้การดูแลระยะยาวง่ายขึ้น
# terminal
# Create virtual environment and install dependencies
python -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
# Install Django 5 and DRF
pip install django djangorestframework
pip install django-filter # Advanced filtering
pip install djangorestframework-simplejwt # JWT authentication
# Create Django project
django-admin startproject config .
python manage.py startapp apiคำสั่งเหล่านี้สร้างโปรเจกต์ Django พร้อมแอปพลิเคชัน api เฉพาะสำหรับ endpoint REST
# config/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third-party apps
'rest_framework',
'rest_framework_simplejwt',
'django_filters',
# Local apps
'api',
]
REST_FRAMEWORK = {
# Default authentication classes
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
# Default permissions: authentication required
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
# Global pagination
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
# Filter backends
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
}การกำหนดค่านี้ตั้งค่าการยืนยันตัวตน JWT, การแบ่งหน้าเริ่มต้น และ filter backends สำหรับ API ทั้งหมด
Creating Data Models
Model ของ Django แสดงโครงสร้างข้อมูลของ API การออกแบบ model อย่างรอบคอบจะช่วยให้การสร้าง serializers และ views ง่ายขึ้น
# api/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.core.validators import MinValueValidator, MaxValueValidator
import uuid
class User(AbstractUser):
"""Custom user model with additional fields."""
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False
)
bio = models.TextField(blank=True, max_length=500)
avatar = models.URLField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.username
class Category(models.Model):
"""Category for organizing articles."""
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(max_length=100, unique=True)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = 'categories'
ordering = ['name']
def __str__(self):
return self.name
class Article(models.Model):
"""Blog article with author and category relations."""
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
('archived', 'Archived'),
]
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False
)
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
content = models.TextField()
excerpt = models.TextField(max_length=300, blank=True)
# Author relation
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='articles'
)
# Category relation
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True,
related_name='articles'
)
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='draft'
)
views_count = models.PositiveIntegerField(default=0)
published_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.titleการใช้ UUID เป็น primary key ช่วยเพิ่มความปลอดภัย (ตัวระบุที่คาดเดาไม่ได้) และรองรับการกระจายข้อมูล
ควรกำหนด custom User model ตั้งแต่เริ่มต้นโปรเจกต์ แม้ยังไม่ต้องการฟิลด์เพิ่มเติม การเปลี่ยนแปลง User model หลังจากทำ migration แรกไปแล้วนั้นซับซ้อนและเสี่ยงต่อการเกิดข้อผิดพลาด
Serializers: Data Transformation and Validation
Serializers เป็นหัวใจหลักของ DRF คลาสเหล่านี้แปลงออบเจกต์ Python เป็น JSON และในทางกลับกัน พร้อมทั้งตรวจสอบความถูกต้องของข้อมูลขาเข้า
# api/serializers.py
from rest_framework import serializers
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from .models import Article, Category
User = get_user_model()
class UserSerializer(serializers.ModelSerializer):
"""Serializer for reading user data."""
# Computed field: number of published articles
articles_count = serializers.SerializerMethodField()
class Meta:
model = User
fields = [
'id', 'username', 'email', 'bio',
'avatar', 'articles_count', 'created_at'
]
# Read-only fields
read_only_fields = ['id', 'created_at']
def get_articles_count(self, obj):
"""Count user's published articles."""
return obj.articles.filter(status='published').count()
class UserCreateSerializer(serializers.ModelSerializer):
"""Serializer for user creation with password validation."""
password = serializers.CharField(
write_only=True,
required=True,
validators=[validate_password],
style={'input_type': 'password'}
)
password_confirm = serializers.CharField(
write_only=True,
required=True,
style={'input_type': 'password'}
)
class Meta:
model = User
fields = [
'id', 'username', 'email', 'password',
'password_confirm', 'bio', 'avatar'
]
def validate(self, attrs):
"""Verify that both passwords match."""
if attrs['password'] != attrs['password_confirm']:
raise serializers.ValidationError({
'password_confirm': 'Passwords do not match.'
})
return attrs
def create(self, validated_data):
"""Create user with hashed password."""
# Remove confirmation field
validated_data.pop('password_confirm')
# Use create_user to hash the password
user = User.objects.create_user(**validated_data)
return user
class CategorySerializer(serializers.ModelSerializer):
"""Serializer for categories with article counter."""
articles_count = serializers.IntegerField(
source='articles.count',
read_only=True
)
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'description', 'articles_count']
class ArticleListSerializer(serializers.ModelSerializer):
"""Lightweight serializer for article listings."""
# Display username instead of UUID
author = serializers.StringRelatedField()
category = serializers.StringRelatedField()
class Meta:
model = Article
fields = [
'id', 'title', 'slug', 'excerpt',
'author', 'category', 'status',
'views_count', 'published_at', 'created_at'
]
class ArticleDetailSerializer(serializers.ModelSerializer):
"""Complete serializer for article details."""
# Include full author data
author = UserSerializer(read_only=True)
category = CategorySerializer(read_only=True)
# Write fields (accepts ID)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
source='category',
write_only=True,
required=False
)
class Meta:
model = Article
fields = [
'id', 'title', 'slug', 'content', 'excerpt',
'author', 'category', 'category_id',
'status', 'views_count',
'published_at', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'author', 'views_count', 'created_at', 'updated_at']
def create(self, validated_data):
"""Automatically assign the author to the logged-in user."""
validated_data['author'] = self.context['request'].user
return super().create(validated_data)การแยก ArticleListSerializer (เบา) และ ArticleDetailSerializer (สมบูรณ์) ช่วยเพิ่มประสิทธิภาพโดยหลีกเลี่ยงการโหลดข้อมูลที่ไม่จำเป็นสำหรับหน้าแสดงรายการ
พร้อมที่จะพิชิตการสัมภาษณ์ Django แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
ViewSets and Automatic Routers
ViewSets รวมการทำงาน CRUD ไว้ในคลาสเดียว เมื่อใช้ร่วมกับ routers แล้ว ViewSets จะสร้าง URL ของ API โดยอัตโนมัติ
# api/views.py
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny, IsAdminUser
from django_filters.rest_framework import DjangoFilterBackend
from django.contrib.auth import get_user_model
from django.utils import timezone
from .models import Article, Category
from .serializers import (
UserSerializer, UserCreateSerializer,
ArticleListSerializer, ArticleDetailSerializer,
CategorySerializer
)
from .permissions import IsAuthorOrReadOnly
from .filters import ArticleFilter
User = get_user_model()
class UserViewSet(viewsets.ModelViewSet):
"""
ViewSet for user management.
Generated endpoints:
- GET /users/ : user list
- POST /users/ : creation (registration)
- GET /users/{id}/ : detail
- PUT/PATCH /users/{id}/ : update
- DELETE /users/{id}/ : delete
- GET /users/me/ : logged-in user profile
"""
queryset = User.objects.all()
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ['username', 'email']
ordering_fields = ['created_at', 'username']
def get_serializer_class(self):
"""Use different serializer for creation."""
if self.action == 'create':
return UserCreateSerializer
return UserSerializer
def get_permissions(self):
"""Dynamic permissions based on action."""
if self.action == 'create':
# Open registration
return [AllowAny()]
if self.action in ['update', 'partial_update', 'destroy']:
# Modification: owner or admin
return [IsAuthenticated()]
return [IsAuthenticated()]
@action(detail=False, methods=['get'])
def me(self, request):
"""Return the logged-in user's profile."""
serializer = self.get_serializer(request.user)
return Response(serializer.data)
@action(detail=False, methods=['patch'])
def update_profile(self, request):
"""Update the logged-in user's profile."""
serializer = self.get_serializer(
request.user,
data=request.data,
partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
class CategoryViewSet(viewsets.ModelViewSet):
"""
ViewSet for category management.
Only admins can create/update/delete.
"""
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = 'slug'
filter_backends = [filters.SearchFilter]
search_fields = ['name', 'description']
def get_permissions(self):
"""Public read, admin-only write."""
if self.action in ['list', 'retrieve']:
return [AllowAny()]
return [IsAdminUser()]
class ArticleViewSet(viewsets.ModelViewSet):
"""
ViewSet for article management.
Features:
- Filter by category, status, author
- Text search
- Sort by date, views
- Custom actions (publish, archive)
"""
queryset = Article.objects.select_related('author', 'category')
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_class = ArticleFilter
search_fields = ['title', 'content', 'excerpt']
ordering_fields = ['created_at', 'published_at', 'views_count']
ordering = ['-created_at']
lookup_field = 'slug'
def get_serializer_class(self):
"""Lightweight serializer for lists, complete for detail."""
if self.action == 'list':
return ArticleListSerializer
return ArticleDetailSerializer
def get_permissions(self):
"""Permissions based on action."""
if self.action in ['list', 'retrieve']:
return [AllowAny()]
if self.action == 'create':
return [IsAuthenticated()]
# Update/delete: author or admin
return [IsAuthorOrReadOnly()]
def get_queryset(self):
"""Filter articles based on user."""
queryset = super().get_queryset()
user = self.request.user
# Unauthenticated users: published articles only
if not user.is_authenticated:
return queryset.filter(status='published')
# Admins: all articles
if user.is_staff:
return queryset
# Authenticated users: published + their own articles
from django.db.models import Q
return queryset.filter(
Q(status='published') | Q(author=user)
)
def retrieve(self, request, *args, **kwargs):
"""Increment view counter on each retrieval."""
instance = self.get_object()
instance.views_count += 1
instance.save(update_fields=['views_count'])
serializer = self.get_serializer(instance)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def publish(self, request, slug=None):
"""Publish a draft article."""
article = self.get_object()
if article.status == 'published':
return Response(
{'error': 'Article already published.'},
status=status.HTTP_400_BAD_REQUEST
)
article.status = 'published'
article.published_at = timezone.now()
article.save()
serializer = self.get_serializer(article)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def archive(self, request, slug=None):
"""Archive a published article."""
article = self.get_object()
article.status = 'archived'
article.save()
serializer = self.get_serializer(article)
return Response(serializer.data)Custom actions (@action) เพิ่ม endpoint เฉพาะเจาะจงเช่น /articles/{slug}/publish/ โดยไม่ต้องสร้าง views ใหม่
Custom Permissions
Permissions ควบคุมการเข้าถึงทรัพยากร DRF ช่วยให้สร้าง permissions ที่ใช้ซ้ำได้สำหรับกฎทางธุรกิจที่ซับซ้อน
# api/permissions.py
from rest_framework import permissions
class IsAuthorOrReadOnly(permissions.BasePermission):
"""
Custom permission:
- Read: everyone
- Write: object author or admin only
"""
def has_object_permission(self, request, view, obj):
# GET, HEAD, OPTIONS methods are always allowed
if request.method in permissions.SAFE_METHODS:
return True
# Write allowed only for author or admins
return obj.author == request.user or request.user.is_staff
class IsOwnerOrAdmin(permissions.BasePermission):
"""
Permission for user resources:
- Users can access their own resources
- Admins can access everything
"""
def has_object_permission(self, request, view, obj):
# Check if object is the user themselves
if hasattr(obj, 'id') and obj.id == request.user.id:
return True
# Check if object belongs to the user
if hasattr(obj, 'user') and obj.user == request.user:
return True
# Admins have access to everything
return request.user.is_staffPermissions เหล่านี้ทำงานที่ระดับออบเจกต์ (has_object_permission) เพื่อควบคุมการเข้าถึงอย่างละเอียดสำหรับแต่ละทรัพยากร
Custom Filters with django-filter
ตัวกรองช่วยให้ client API ค้นหาและกรองข้อมูลตามเกณฑ์ต่างๆ ได้
# api/filters.py
import django_filters
from .models import Article
class ArticleFilter(django_filters.FilterSet):
"""
Custom filters for articles.
Usage examples:
- /articles/?category=tech
- /articles/?status=published
- /articles/?author=username
- /articles/?created_after=2026-01-01
- /articles/?min_views=100
"""
# Filter by category slug
category = django_filters.CharFilter(
field_name='category__slug',
lookup_expr='exact'
)
# Filter by author username
author = django_filters.CharFilter(
field_name='author__username',
lookup_expr='exact'
)
# Filter by creation date (after)
created_after = django_filters.DateFilter(
field_name='created_at',
lookup_expr='gte'
)
# Filter by creation date (before)
created_before = django_filters.DateFilter(
field_name='created_at',
lookup_expr='lte'
)
# Filter by minimum views
min_views = django_filters.NumberFilter(
field_name='views_count',
lookup_expr='gte'
)
# Filter by title (contains)
title = django_filters.CharFilter(
field_name='title',
lookup_expr='icontains'
)
class Meta:
model = Article
fields = ['status', 'category', 'author']ตัวกรองเหล่านี้สร้างเอกสารประกอบในอินเตอร์เฟซ browsable ของ DRF โดยอัตโนมัติ
ตัวกรองบนฟิลด์ข้อความที่ใช้ icontains อาจช้าบนตารางขนาดใหญ่ สำหรับการค้นหาข้อความเต็มรูปแบบ ควรพิจารณาใช้ PostgreSQL กับ SearchVector หรือ Elasticsearch
URL Configuration and Routers
Router ของ DRF สร้าง URL RESTful จาก ViewSets ที่ลงทะเบียนแล้วโดยอัตโนมัติ
# api/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView,
)
from .views import UserViewSet, CategoryViewSet, ArticleViewSet
# Create router with automatic URL generation
router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
router.register(r'categories', CategoryViewSet, basename='category')
router.register(r'articles', ArticleViewSet, basename='article')
urlpatterns = [
# Router-generated URLs
path('', include(router.urls)),
# JWT authentication endpoints
path('auth/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('auth/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('auth/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]# config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# All API URLs under /api/
path('api/', include('api.urls')),
]การกำหนดค่านี้เปิดให้ใช้งาน endpoint ต่อไปนี้:
POST /api/auth/token/: รับ token JWTPOST /api/auth/token/refresh/: ต่ออายุ tokenGET/POST /api/users/: รายการและสร้างผู้ใช้GET/PUT/PATCH/DELETE /api/users/{id}/: การทำงานกับผู้ใช้- และเช่นเดียวกันสำหรับ categories และ articles
Advanced JWT Configuration
การยืนยันตัวตน JWT ต้องการการกำหนดค่าที่เหมาะสมสำหรับความปลอดภัยและประสบการณ์ผู้ใช้
# config/settings.py
from datetime import timedelta
SIMPLE_JWT = {
# Access token lifetime
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
# Refresh token lifetime
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
# Automatic refresh token rotation
'ROTATE_REFRESH_TOKENS': True,
# Blacklist old tokens after rotation
'BLACKLIST_AFTER_ROTATION': True,
# Signing algorithm
'ALGORITHM': 'HS256',
# Signing key (use secret key in production)
'SIGNING_KEY': SECRET_KEY,
# Authorization header prefix
'AUTH_HEADER_TYPES': ('Bearer',),
# Fields included in token
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
}การหมุนเวียน token (ROTATE_REFRESH_TOKENS) เพิ่มความปลอดภัยโดยทำให้ token เก่าเป็นโมฆะหลังจากการต่ออายุแต่ละครั้ง
Automated API Testing
DRF มีเครื่องมือทดสอบในตัวสำหรับตรวจสอบพฤติกรรมของ API
# api/tests/test_articles.py
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
from django.contrib.auth import get_user_model
from api.models import Article, Category
User = get_user_model()
class ArticleAPITestCase(TestCase):
"""Tests for article endpoints."""
def setUp(self):
"""Set up test data."""
self.client = APIClient()
# Create test user
self.user = User.objects.create_user(
username='testuser',
email='test@example.com',
password='testpass123'
)
# Create category
self.category = Category.objects.create(
name='Tech',
slug='tech',
description='Technology articles'
)
# Create published article
self.article = Article.objects.create(
title='Test Article',
slug='test-article',
content='Detailed test content.',
excerpt='Short summary',
author=self.user,
category=self.category,
status='published'
)
def test_list_articles_unauthenticated(self):
"""Published articles are accessible without authentication."""
url = reverse('article-list')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['results']), 1)
def test_list_articles_filters_drafts(self):
"""Drafts are not visible to unauthenticated users."""
# Create a draft
Article.objects.create(
title='Draft Article',
slug='draft-article',
content='Draft content',
author=self.user,
status='draft'
)
url = reverse('article-list')
response = self.client.get(url)
# Only published article is visible
self.assertEqual(len(response.data['results']), 1)
def test_create_article_authenticated(self):
"""An authenticated user can create an article."""
self.client.force_authenticate(user=self.user)
url = reverse('article-list')
data = {
'title': 'New Article',
'slug': 'new-article',
'content': 'New article content.',
'category_id': self.category.id,
}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Article.objects.count(), 2)
# Author is automatically assigned
self.assertEqual(
Article.objects.get(slug='new-article').author,
self.user
)
def test_create_article_unauthenticated(self):
"""An unauthenticated user cannot create an article."""
url = reverse('article-list')
data = {
'title': 'Unauthorized Article',
'slug': 'unauthorized-article',
'content': 'Content',
}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
def test_update_own_article(self):
"""An author can update their own article."""
self.client.force_authenticate(user=self.user)
url = reverse('article-detail', kwargs={'slug': self.article.slug})
data = {'title': 'Modified Title'}
response = self.client.patch(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.article.refresh_from_db()
self.assertEqual(self.article.title, 'Modified Title')
def test_update_other_user_article(self):
"""A user cannot update another user's article."""
other_user = User.objects.create_user(
username='other',
email='other@example.com',
password='otherpass123'
)
self.client.force_authenticate(user=other_user)
url = reverse('article-detail', kwargs={'slug': self.article.slug})
data = {'title': 'Modified Title'}
response = self.client.patch(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_publish_action(self):
"""The publish action changes the article status."""
draft = Article.objects.create(
title='Draft',
slug='draft',
content='Draft content',
author=self.user,
status='draft'
)
self.client.force_authenticate(user=self.user)
url = reverse('article-publish', kwargs={'slug': draft.slug})
response = self.client.post(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
draft.refresh_from_db()
self.assertEqual(draft.status, 'published')
self.assertIsNotNone(draft.published_at)
def test_filter_by_category(self):
"""Filtering by category works correctly."""
url = reverse('article-list')
response = self.client.get(url, {'category': 'tech'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['results']), 1)
def test_search_articles(self):
"""Text search works."""
url = reverse('article-list')
response = self.client.get(url, {'search': 'Test'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['results']), 1)รันทดสอบด้วยคำสั่ง python manage.py test api.tests
Error Handling and Standardized Responses
การจัดการข้อผิดพลาดอย่างสม่ำเสมอช่วยเพิ่มประสบการณ์การใช้งานสำหรับนักพัฒนาที่เรียกใช้ API
# api/exceptions.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
from rest_framework import status
def custom_exception_handler(exc, context):
"""
Custom exception handler to standardize error responses.
Response format:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Error description",
"details": {...} # Optional
}
}
"""
# Call the default handler
response = exception_handler(exc, context)
if response is not None:
# Standardize response format
custom_response = {
'success': False,
'error': {
'code': get_error_code(exc),
'message': get_error_message(response.data),
'details': response.data if isinstance(response.data, dict) else None
}
}
response.data = custom_response
return response
def get_error_code(exc):
"""Return an error code based on exception type."""
error_codes = {
'ValidationError': 'VALIDATION_ERROR',
'AuthenticationFailed': 'AUTHENTICATION_FAILED',
'NotAuthenticated': 'NOT_AUTHENTICATED',
'PermissionDenied': 'PERMISSION_DENIED',
'NotFound': 'NOT_FOUND',
'MethodNotAllowed': 'METHOD_NOT_ALLOWED',
'Throttled': 'RATE_LIMIT_EXCEEDED',
}
return error_codes.get(exc.__class__.__name__, 'UNKNOWN_ERROR')
def get_error_message(data):
"""Extract a readable error message from response data."""
if isinstance(data, dict):
if 'detail' in data:
return str(data['detail'])
# Collect validation messages
messages = []
for field, errors in data.items():
if isinstance(errors, list):
messages.extend([f"{field}: {e}" for e in errors])
else:
messages.append(f"{field}: {errors}")
return '; '.join(messages) if messages else 'Validation error'
return str(data)# config/settings.py
REST_FRAMEWORK = {
# ... other configurations
'EXCEPTION_HANDLER': 'api.exceptions.custom_exception_handler',
}Conclusion
Django REST Framework ร่วมกับ Django 5 มอบระบบนิเวศที่สมบูรณ์สำหรับการสร้าง REST API ระดับมืออาชีพ พลังของ serializers ความยืดหยุ่นของ ViewSets และการทำงานร่วมกับการยืนยันตัวตน JWT แบบ native ช่วยให้สร้าง API ที่แข็งแกร่งและปลอดภัยได้อย่างรวดเร็ว
รายการตรวจสอบสำหรับ API Django คุณภาพ
- ใช้ serializer แยกต่างหากสำหรับการอ่านและเขียน
- ใช้งาน custom permissions สำหรับควบคุมการเข้าถึง
- กำหนดค่าตัวกรองสำหรับการค้นหาและเรียงลำดับ
- เพิ่มการยืนยันตัวตน JWT พร้อมการหมุนเวียน token
- เขียน unit test สำหรับแต่ละ endpoint
- ทำรูปแบบการตอบกลับข้อผิดพลาดให้เป็นมาตรฐาน
- ทำเอกสารประกอบ API ผ่าน DRF Spectacular หรือ drf-yasg
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แนวทางของ DRF ส่งเสริมการใช้ซ้ำและการประกอบกัน: serializers, permissions และตัวกรองทำงานร่วมกันเพื่อสร้าง API ที่ดูแลรักษาง่ายในระยะยาว เอกสารประกอบอัตโนมัติและอินเตอร์เฟซ browsable ช่วยเร่งการพัฒนาและอำนวยความสะดวกในการเชื่อมต่อสำหรับทีม frontend
แท็ก
แชร์
บทความที่เกี่ยวข้อง

คำถามสัมภาษณ์งาน Django และ Python: 25 คำถามยอดนิยมประจำปี 2026
25 คำถามสัมภาษณ์งาน Django และ Python ที่พบบ่อยที่สุด ORM, views, middleware, DRF, signals และการปรับแต่งประสิทธิภาพ พร้อมคำตอบละเอียดและตัวอย่างโค้ด

คำถามสัมภาษณ์งาน Django: ORM, Middleware และ DRF เจาะลึก
คำถามสัมภาษณ์งาน Django ครอบคลุมการปรับแต่ง ORM ด้วย select_related และ prefetch_related, สถาปัตยกรรม middleware, ประสิทธิภาพ serializer ของ Django REST Framework, permissions และรูปแบบ pagination

Symfony 7 กับ API Platform: แนวทางปฏิบัติที่ดีที่สุดสำหรับ REST API
คู่มือเชิงลึกสำหรับการสร้าง REST API ด้วย Symfony 7 และ API Platform 4 ครอบคลุม Resource configuration, State Processor, Serialization Groups, Security และการปรับแต่ง performance ด้วยตัวอย่างโค้ดที่ใช้งานได้จริง