Django REST Framework Views Decoded: Choosing Between FBVs, APIView, Generics, and ViewSets

Stop Copy-Pasting DRF Code: Here’s How to Choose the Right View

You’re building a Django REST API. You search “DRF list view” and find five different ways to do the same thing. Should you use a function-based view? APIView? GenericAPIView? V…


This content originally appeared on DEV Community and was authored by sizan mahmud0

Stop Copy-Pasting DRF Code: Here's How to Choose the Right View

You're building a Django REST API. You search "DRF list view" and find five different ways to do the same thing. Should you use a function-based view? APIView? GenericAPIView? ViewSets? The documentation doesn't help—it shows you how but not when.

I've built APIs with all five approaches. Today, I'll show you exactly when to use each one, how they work under the hood, and which choice will save you the most time based on your project's needs.

The Five Levels of DRF Views: A Progression

Think of DRF views as a ladder from maximum control to maximum convenience:

Function-Based Views (FBV)          ← Most control, most code
    ↓
APIView                             ← OOP structure, manual logic
    ↓
GenericAPIView + Mixins             ← Reusable components
    ↓
Concrete Generic Views              ← Pre-built common patterns
    ↓
ViewSets + Routers                  ← Least code, convention over configuration

Each level trades some control for less code. Let's explore when each makes sense.

Level 1: Function-Based Views (FBVs) - Full Control

When to use:

  • Quick prototypes or simple endpoints
  • Highly custom logic that doesn't fit DRF patterns
  • One-off operations (password reset, webhook receivers)
  • Learning DRF fundamentals

How they work:

FBVs are decorated Python functions that receive a Request object and return a Response:

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Product
from .serializers import ProductSerializer

@api_view(['GET', 'POST'])
def product_list(request):
    if request.method == 'GET':
        products = Product.objects.all()
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = ProductSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET'])
def product_detail(request, pk):
    try:
        product = Product.objects.get(pk=pk)
    except Product.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    serializer = ProductSerializer(product)
    return Response(serializer.data)

Pros:

  • Simple and explicit—easy to understand
  • No classes or inheritance confusion
  • Perfect for learning

Cons:

  • Lots of boilerplate code
  • Manual error handling
  • No built-in pagination, filtering, or permissions
  • Harder to reuse logic across endpoints

Use case: A custom webhook endpoint that processes payment confirmations with unique business logic.

Level 2: APIView - Object-Oriented Structure

When to use:

  • Need more structure than FBVs
  • Custom endpoints with specific HTTP method logic
  • Want class-based organization without DRF's assumptions

How they work:

APIView is a class-based view where each HTTP method maps to a class method:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class ProductList(APIView):
    def get(self, request):
        products = Product.objects.all()
        serializer = ProductSerializer(products, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = ProductSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class ProductDetail(APIView):
    def get_object(self, pk):
        try:
            return Product.objects.get(pk=pk)
        except Product.DoesNotExist:
            raise Http404

    def get(self, request, pk):
        product = self.get_object(pk)
        serializer = ProductSerializer(product)
        return Response(serializer.data)

    def put(self, request, pk):
        product = self.get_object(pk)
        serializer = ProductSerializer(product, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        product = self.get_object(pk)
        product.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Pros:

  • Better organization than FBVs
  • Can share logic via methods (get_object)
  • Easier to add authentication/permissions
  • Class-based inheritance for code reuse

Cons:

  • Still writing repetitive CRUD logic
  • Manual pagination and filtering
  • More code than necessary for standard patterns

Use case: A dashboard endpoint that aggregates data from multiple models with custom business logic.

Level 3: GenericAPIView + Mixins - Component Assembly

When to use:

  • Standard CRUD but need some customization
  • Want to pick and choose features (only list + create, not delete)
  • Learning how DRF's generics work internally

How they work:

GenericAPIView provides base functionality (queryset, serializer), while Mixins add specific actions:

from rest_framework import generics, mixins

class ProductList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class ProductDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

Available Mixins:

  • ListModelMixin - List queryset
  • CreateModelMixin - Create instance
  • RetrieveModelMixin - Get single instance
  • UpdateModelMixin - Update instance
  • DestroyModelMixin - Delete instance

Pros:

  • Pick exactly what HTTP methods you need
  • Built-in pagination, filtering, authentication
  • Less code than APIView
  • Clear about what operations are supported

Cons:

  • Verbose—must map each method manually
  • Why not just use Concrete Views?

Use case: Rarely used in practice—better to jump to Level 4. Useful for learning DRF internals.

Level 4: Concrete Generic Views - The Sweet Spot

When to use:

  • Standard CRUD operations (90% of API endpoints)
  • Need quick development with minimal code
  • Want built-in pagination, filtering, permissions
  • Building RESTful APIs following conventions

How they work:

Concrete views combine GenericAPIView + Mixins into ready-to-use classes:

from rest_framework import generics

class ProductList(generics.ListCreateAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    # That's it! List + Create in 3 lines

class ProductDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    # Retrieve + Update + Delete in 3 lines

Available Concrete Views:

  • ListAPIView - Read-only list
  • CreateAPIView - Create only
  • RetrieveAPIView - Read-only detail
  • UpdateAPIView - Update only
  • DestroyAPIView - Delete only
  • ListCreateAPIView - List + Create
  • RetrieveUpdateAPIView - Retrieve + Update
  • RetrieveDestroyAPIView - Retrieve + Delete
  • RetrieveUpdateDestroyAPIView - Full detail CRUD

Customization example:

class ProductList(generics.ListCreateAPIView):
    serializer_class = ProductSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        # Custom filtering logic
        user = self.request.user
        return Product.objects.filter(owner=user, is_active=True)

    def perform_create(self, serializer):
        # Custom save logic
        serializer.save(owner=self.request.user)

Pros:

  • Minimal code for standard patterns
  • Built-in pagination, filtering, ordering
  • Easy to customize via method overrides
  • Clear, readable, maintainable

Cons:

  • Less flexible than APIView for unique endpoints
  • URL routing still requires manual configuration

Use case: 90% of your API endpoints—user management, product listings, order history, blog posts.

Level 5: ViewSets + Routers - Convention Over Configuration

When to use:

  • Full CRUD for a resource
  • Want automatic URL routing
  • Building large APIs quickly
  • Following RESTful conventions strictly

How they work:

ViewSets combine all CRUD operations in one class, and Routers generate URLs automatically:

from rest_framework import viewsets

class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    permission_classes = [IsAuthenticated]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

# urls.py
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'products', ProductViewSet)

urlpatterns = [
    path('api/', include(router.urls)),
]

This automatically generates:

GET     /api/products/          → list()
POST    /api/products/          → create()
GET     /api/products/{pk}/     → retrieve()
PUT     /api/products/{pk}/     → update()
PATCH   /api/products/{pk}/     → partial_update()
DELETE  /api/products/{pk}/     → destroy()

Available ViewSets:

  • ViewSet - Base class, manual action mapping
  • GenericViewSet - Adds queryset/serializer
  • ModelViewSet - Full CRUD (most common)
  • ReadOnlyModelViewSet - Only list + retrieve

Custom actions:

class ProductViewSet(viewsets.ModelViewSet):
    # ... base config

    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        product = self.get_object()
        product.is_published = True
        product.save()
        return Response({'status': 'published'})

    # Creates: POST /api/products/{pk}/publish/

Pros:

  • Least code for full CRUD
  • Automatic URL generation
  • Organized resource-centric structure
  • Perfect for RESTful APIs

Cons:

  • Can be confusing for non-standard endpoints
  • Less explicit than Concrete Views
  • URL customization requires learning Router options

Use case: Building a full REST API with multiple resources (users, products, orders, reviews)—ViewSets eliminate hundreds of lines of boilerplate.

Decision Tree: Which View Should I Use?

Choose Function-Based Views if:

  • Building a single custom endpoint (webhook, health check)
  • Prototyping quickly
  • Logic doesn't fit REST patterns

Choose APIView if:

  • Need custom logic per HTTP method
  • Endpoint doesn't map to a model (analytics, reports)
  • Want class structure without DRF assumptions

Choose Concrete Generic Views if:

  • Building standard CRUD for resources
  • Want balance of power and simplicity
  • Need customization per endpoint
  • This is the sweet spot for most projects

Choose ViewSets if:

  • Building full CRUD for multiple resources
  • Want auto-generated URLs
  • Strictly following REST conventions
  • Scaling to dozens of similar endpoints

Skip GenericAPIView + Mixins unless you're learning DRF internals—use Concrete Views instead.

My Recommendation: Start with Concrete Generic Views

For 90% of projects, ListCreateAPIView and RetrieveUpdateDestroyAPIView provide the perfect balance:

# Your typical API in 10 lines
class ProductList(generics.ListCreateAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

class ProductDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

Upgrade to ViewSets when you have 5+ similar resources. Drop down to APIView or FBVs when you need custom logic.

Conclusion

DRF gives you flexibility, but that flexibility can be overwhelming. Remember:

  • FBVs/APIView: Maximum control, custom logic
  • Concrete Generic Views: Sweet spot for most REST APIs
  • ViewSets: Maximum productivity for large, consistent APIs

Start simple, refactor when patterns emerge. Your API will thank you.

Found this helpful? 👏 Give it a clap!

Want more Django + DRF deep dives? 🔔 Follow me for practical backend engineering content.

Help a fellow developer! 📤 Share this guide with someone confused about DRF views.

What's your go-to view type? Drop your preference in the comments! 💬

Tags: #Django #DjangoRESTFramework #DRF #Python #BackendDevelopment #API #WebDevelopment #REST


This content originally appeared on DEV Community and was authored by sizan mahmud0


Print Share Comment Cite Upload Translate Updates
APA

sizan mahmud0 | Sciencx (2025-10-26T06:12:53+00:00) Django REST Framework Views Decoded: Choosing Between FBVs, APIView, Generics, and ViewSets. Retrieved from https://www.scien.cx/2025/10/26/django-rest-framework-views-decoded-choosing-between-fbvs-apiview-generics-and-viewsets/

MLA
" » Django REST Framework Views Decoded: Choosing Between FBVs, APIView, Generics, and ViewSets." sizan mahmud0 | Sciencx - Sunday October 26, 2025, https://www.scien.cx/2025/10/26/django-rest-framework-views-decoded-choosing-between-fbvs-apiview-generics-and-viewsets/
HARVARD
sizan mahmud0 | Sciencx Sunday October 26, 2025 » Django REST Framework Views Decoded: Choosing Between FBVs, APIView, Generics, and ViewSets., viewed ,<https://www.scien.cx/2025/10/26/django-rest-framework-views-decoded-choosing-between-fbvs-apiview-generics-and-viewsets/>
VANCOUVER
sizan mahmud0 | Sciencx - » Django REST Framework Views Decoded: Choosing Between FBVs, APIView, Generics, and ViewSets. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/10/26/django-rest-framework-views-decoded-choosing-between-fbvs-apiview-generics-and-viewsets/
CHICAGO
" » Django REST Framework Views Decoded: Choosing Between FBVs, APIView, Generics, and ViewSets." sizan mahmud0 | Sciencx - Accessed . https://www.scien.cx/2025/10/26/django-rest-framework-views-decoded-choosing-between-fbvs-apiview-generics-and-viewsets/
IEEE
" » Django REST Framework Views Decoded: Choosing Between FBVs, APIView, Generics, and ViewSets." sizan mahmud0 | Sciencx [Online]. Available: https://www.scien.cx/2025/10/26/django-rest-framework-views-decoded-choosing-between-fbvs-apiview-generics-and-viewsets/. [Accessed: ]
rf:citation
» Django REST Framework Views Decoded: Choosing Between FBVs, APIView, Generics, and ViewSets | sizan mahmud0 | Sciencx | https://www.scien.cx/2025/10/26/django-rest-framework-views-decoded-choosing-between-fbvs-apiview-generics-and-viewsets/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.