Thay thế Supabase bằng Django + DRF: xây API bảo mật, dễ mở rộng
Supabase rất hấp dẫn: Postgres, Auth, Storage, Realtime, dashboard đẹp, triển khai nhanh. Với MVP, nội bộ tool, sản phẩm cần ra thị trường sớm → Supabase là lựa chọn mạnh.
Nhưng khi sản phẩm lớn dần, nhiều team gặp cùng câu hỏi: có nên tự xây backend bằng Django + Django REST Framework thay Supabase không?
Câu trả lời: có, nếu bạn cần kiểm soát sâu hơn về logic nghiệp vụ, bảo mật, phân quyền, tích hợp hệ thống, vận hành dài hạn. Django + DRF không phải “nhanh hơn Supabase” ở ngày đầu. Nhưng khi app phức tạp, nó cho bạn thứ rất quan trọng: quyền kiểm soát kiến trúc.
Bài này phân tích cách thay Supabase bằng Django + DRF theo hướng thực tế: auth, API, bảo mật, mở rộng, vận hành.
Vì sao muốn rời Supabase?
Supabase giải quyết nhiều bài toán phổ biến:
– DB Postgres. – Auth. – REST/GraphQL-like API. – Realtime. – Storage. – Edge Functions. – Row Level Security.
Vấn đề xuất hiện khi logic vượt khỏi CRUD:
– Phân quyền phức tạp theo tổ chức, vai trò, trạng thái dữ liệu. – Workflow nhiều bước: duyệt, khóa, audit, rollback. – Tích hợp payment, CRM, ERP, webhook. – Cần domain service rõ ràng. – Cần test nghiệp vụ nghiêm túc. – Cần kiểm soát request/response, logging, rate limit, observability. – Cần self-host, compliance, dữ liệu nhạy cảm.
Supabase mạnh ở “backend-as-a-service”. Django mạnh ở “application backend”. Khác nhau nằm ở mức trừu tượng.
Django + DRF thay thế phần nào của Supabase?
Khi chuyển sang Django + DRF, mapping thường như sau:
| Supabase | Django + DRF | |—|—| | Postgres | PostgreSQL + Django ORM | | Supabase Auth | Django auth / SimpleJWT / OAuth | | Auto API | DRF ViewSet / APIView | | RLS | Permission class / object permission / query scoping | | Edge Functions | Django service layer / Celery task | | Storage | S3/MinIO + django-storages | | Realtime | Django Channels / WebSocket / polling | | Dashboard | Django Admin | | Logs | structlog / Sentry / OpenTelemetry |
Điểm chính: Supabase cho nhiều thứ sẵn. Django bắt bạn thiết kế. Đổi lại, thiết kế đó thuộc về bạn.
Kiến trúc khuyến nghị
Một cấu trúc đơn giản, đủ mở rộng:
project/
config/
settings.py
urls.py
apps/
accounts/
organizations/
billing/
projects/
audit/
common/
permissions.py
pagination.py
exceptions.py
throttling.pyMỗi app nên có:
models.py
serializers.py
views.py
permissions.py
services.py
selectors.py
tests/Quy ước hữu ích:
– models.py → cấu trúc dữ liệu. – serializers.py → validate input/output. – views.py → HTTP layer. – services.py → nghiệp vụ ghi dữ liệu. – selectors.py → nghiệp vụ đọc dữ liệu. – permissions.py → kiểm soát truy cập. – tests/ → bảo vệ logic.
Tránh nhét toàn bộ logic vào ViewSet hoặc Serializer. Ban đầu nhanh. Về sau khó test, khó reuse, khó debug.
Thiết kế Auth: từ “đăng nhập được” đến “an toàn”
Supabase Auth tiện. Django cần cấu hình kỹ hơn.
Phổ biến nhất với API:
– JWT access token ngắn hạn. – Refresh token dài hạn. – Rotation refresh token nếu cần bảo mật cao. – Logout → blacklist token. – MFA nếu app nhạy cảm. – OAuth Google/GitHub nếu cần.
Thư viện thường dùng:
djangorestframework-simplejwt
django-allauth
dj-rest-authCấu hình nên có:
– Access token TTL ngắn: 5–15 phút. – Refresh token TTL: 7–30 ngày tùy app. – HTTPS bắt buộc. – Cookie HttpOnly nếu frontend web cần giảm rủi ro XSS token theft. – Rate limit login. – Lock/throttle khi brute force. – Email verification.
Ví dụ permission cơ bản:
from rest_framework.permissions import IsAuthenticated
class ProjectViewSet(ModelViewSet):
permission_classes = [IsAuthenticated]
Nhưng thực tế cần hơn vậy: user chỉ thấy dữ liệu thuộc organization của họ.
def get_queryset(self):
return Project.objects.filter(
organization__members__user=self.request.user
)Đây là “RLS kiểu Django”: không dựa vào DB policy, mà enforce ở query + permission layer.
Phân quyền: thay RLS bằng permission rõ ràng
Supabase RLS rất mạnh nếu viết đúng. Nhưng policy SQL dễ phân tán, khó đọc với dev backend không quen.
Django + DRF có nhiều tầng:
1. Endpoint permission
Ai được gọi API?
class IsOrgMember(BasePermission):
def has_permission(self, request, view):
org_id = view.kwargs.get("org_id")
return OrganizationMember.objects.filter(
organization_id=org_id,
user=request.user
).exists()2. Object permission
Ai được thao tác object cụ thể?
class CanEditProject(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.organization.members.filter(
user=request.user,
role__in=["owner", "admin"]
).exists()3. Query scoping
User chỉ query dữ liệu được phép thấy.
Project.objects.filter(organization__members__user=user)Quy tắc vàng: permission không chỉ chặn action, còn phải giới hạn queryset. Nếu chỉ check khi update/delete nhưng list trả toàn bộ object → leak dữ liệu.
API design: đừng chỉ CRUD
DRF rất tốt cho CRUD:
class ProjectViewSet(ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializerNhưng sản phẩm thực tế cần action nghiệp vụ:
– Submit invoice. – Approve request. – Cancel subscription. – Invite member. – Transfer ownership. – Archive project.
Không nên map mọi thứ thành update field. Ví dụ thay vì:
PATCH /projects/123
{"status": "approved"}Nên dùng:
POST /projects/123/approveLợi ích:
– Rõ intent. – Dễ log. – Dễ permission. – Dễ validate transition. – Dễ audit.
Trong DRF:
@action(detail=True, methods=["post"])
def approve(self, request, pk=None):
project = self.get_object()
approve_project(project, request.user)
return Response(ProjectSerializer(project).data)Nghiệp vụ nằm trong service:
def approve_project(project, user):
if not user_can_approve(user, project):
raise PermissionDenied()
if project.status != "pending":
raise ValidationError("Invalid state")
project.status = "approved"
project.save(update_fields=["status"])View → mỏng. Service → rõ. Test → dễ.
Bảo mật API: checklist bắt buộc
Khi tự thay Supabase, bảo mật là trách nhiệm của bạn.
Cấu hình production
– DEBUG = False.
– ALLOWED_HOSTS đúng.
– HTTPS.
– Secure cookie.
– HSTS.
– CORS chặt.
– Secret qua env, không commit.
– DB user quyền tối thiểu.
DRF settings
Nên cấu hình:
– Authentication mặc định. – Permission mặc định. – Pagination mặc định. – Throttle mặc định. – Renderer production chỉ JSON. – Exception handler chuẩn hóa lỗi.
Ví dụ:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.UserRateThrottle",
"rest_framework.throttling.AnonRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"user": "1000/day",
"anon": "100/day",
},
}Validate input
Serializer không chỉ để serialize. Nó là lớp phòng thủ đầu tiên.
– Check field bắt buộc.
– Giới hạn length.
– Validate enum.
– Validate trạng thái.
– Không expose field nhạy cảm.
– Dùng read_only_fields.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "email", "name"]
read_only_fields = ["id", "email"]Không bao giờ trả:
– password hash. – secret token. – internal note. – payment raw response. – permission debug info.
Database: vẫn là Postgres, nhưng bạn kiểm soát tốt hơn
Supabase dùng Postgres. Django cũng nên dùng Postgres. Không cần đổi DB nếu không cần.
Điểm cần chú ý:
– Migration bằng Django.
– Index cho query lớn.
– Constraint ở DB.
– Transaction cho workflow.
– select_related, prefetch_related.
– Tránh N+1 query.
– Soft delete nếu nghiệp vụ cần.
– Audit table cho dữ liệu quan trọng.
Ví dụ transaction:
from django.db import transaction
@transaction.atomic
def transfer_ownership(org, old_owner, new_owner):
change_role(old_owner, "admin")
change_role(new_owner, "owner")
create_audit_log(org, "ownership_transferred")
Không dùng transaction → lỗi giữa chừng → dữ liệu lệch.
Storage: thay Supabase Storage bằng S3/MinIO
Nếu app upload file, lựa chọn phổ biến:
– AWS S3. – Cloudflare R2. – MinIO self-host. – DigitalOcean Spaces.
Dùng:
django-storages
boto3Best practice:
– Upload qua presigned URL. – File private mặc định. – Check content type. – Giới hạn size. – Virus scan nếu file nhạy cảm. – Không tin filename từ client. – Lưu metadata trong DB.
Model mẫu:
class Document(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
file = models.FileField(upload_to="documents/")
size = models.PositiveIntegerField()
content_type = models.CharField(max_length=100)Realtime: có cần thay ngay không?
Supabase Realtime tiện. Django không mặc định realtime.
Các lựa chọn:
– Polling đơn giản: đủ cho nhiều dashboard. – Server-Sent Events: một chiều, nhẹ. – WebSocket với Django Channels. – External pub/sub: Redis, NATS, Ably, Pusher.
Đừng tự xây realtime nếu sản phẩm chưa cần. Realtime tăng độ phức tạp: connection state, auth, scale, retry, backpressure.
Khuyến nghị thực tế:
1. Bắt đầu với polling. 2. Chỉ dùng WebSocket cho case thật sự cần. 3. Tách event layer sớm nếu app lớn.
Background jobs: thứ Supabase không luôn giải quyết gọn
Django + Celery rất mạnh cho job nền:
– Gửi email. – Sync webhook. – Generate report. – Resize ảnh. – Billing retry. – Data import/export. – Notification.
Stack phổ biến:
Celery + Redis/RabbitMQ
django-celery-beat
django-celery-resultsNguyên tắc:
– API request không xử lý tác vụ lâu. – Tạo job → trả response nhanh. – Job idempotent. – Retry có giới hạn. – Log lỗi vào Sentry.
Observability: tự host thì phải nhìn thấy hệ thống
Supabase có dashboard sẵn. Django cần tự lắp:
– Logging JSON. – Request ID. – Error tracking: Sentry. – Metrics: Prometheus/Grafana. – Tracing: OpenTelemetry. – Audit log nghiệp vụ. – Health check endpoint.
Tối thiểu production nên có:
GET /healthz
GET /readyzVà log các event quan trọng:
– login failed. – permission denied. – payment failed. – ownership changed. – token revoked. – data export requested.
Khi nào không nên rời Supabase?
Không phải dự án nào cũng cần Django.
Giữ Supabase nếu:
– Team nhỏ, cần ship nhanh. – App chủ yếu CRUD. – Phân quyền đơn giản. – Không có compliance nặng. – Không muốn vận hành backend. – Budget backend hạn chế. – Realtime/storage/auth managed là ưu tiên.
Chọn Django + DRF nếu:
– Logic nghiệp vụ phức tạp. – API cần ổn định dài hạn. – Team backend mạnh. – Cần kiểm soát bảo mật. – Cần tích hợp nhiều hệ thống. – Cần test sâu. – Cần self-host hoặc cloud-neutral.
Lộ trình migration thực tế
Đừng “big bang rewrite”. Rủi ro cao.
Cách an toàn:
Giai đoạn 1: Đóng băng schema chính
– Audit bảng Supabase. – Xác định domain model. – Dọn field thừa. – Ghi rõ ownership dữ liệu.
Giai đoạn 2: Dựng Django đọc DB
– Kết nối cùng Postgres nếu có thể. – Map model. – Tạo endpoint read-only. – So sánh response với frontend hiện tại.
Giai đoạn 3: Chuyển auth
– Quyết định JWT/session/OAuth. – Migration user. – Reset password nếu cần. – Gửi thông báo user nếu thay đổi login flow.
Giai đoạn 4: Chuyển write API
– Ưu tiên module ít rủi ro. – Viết test nghiệp vụ. – Bật dual-write nếu cần. – Monitor lỗi.
Giai đoạn 5: Tắt dần Supabase feature
– Storage. – Edge Functions. – Realtime. – RLS policy. – Client SDK dependency.
Migration tốt là migration có rollback plan.
Kết luận: Supabase để đi nhanh, Django để đi sâu
Supabase không “kém”. Django + DRF không “tự động tốt hơn”. Chúng phục vụ hai giai đoạn khác nhau.
Nếu bạn cần MVP nhanh, Supabase giúp tiết kiệm hàng tuần. Nếu sản phẩm đã có nghiệp vụ phức tạp, phân quyền nhiều lớp, yêu cầu bảo mật cao, tích hợp sâu, vận hành lâu dài → Django + DRF là nền tảng rất đáng tin.
Công thức thực tế:
– Dùng Postgres tiếp. – Thiết kế auth chắc. – Scope queryset nghiêm. – Viết permission rõ. – Đưa nghiệp vụ vào service layer. – Test workflow quan trọng. – Bật logging, monitoring, rate limit. – Migration từng phần, không rewrite ồ ạt.
Thay Supabase bằng Django + DRF không chỉ là đổi công nghệ. Đó là bước chuyển từ “backend dựng sẵn” sang “backend thuộc quyền kiểm soát của bạn”. Khi sản phẩm đủ lớn, quyền kiểm soát đó thường đáng giá.
Bình luận (0)
Chưa có bình luận. Hãy là người đầu tiên!