Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 30

Implementing Search and Filtering in API Endpoints

Recommended resources

Welcome back! In our last lesson, you made your API more scalable and user-friendly by implementing pagination. Now, instead of sending a massive, unwieldy list of tasks, your API serves them in neat, manageable pages. This is a huge step towards building a production-ready system.

Today, we'll give your API another layer of sophistication: filtering, searching, and ordering. Pagination controls how much data is returned, but filtering controls which data is returned. This empowers clients to request exactly what they need—for example, only "in-progress" tasks, tasks containing the word "report," or tasks sorted by priority.

By the end of this lesson, you will be able to:

  • Implement exact-match filtering for specific model fields.
  • Add a text-based search capability to your API endpoint.
  • Allow clients to order the result set based on different attributes.
  • Create a custom FilterSet for advanced filtering logic, such as date ranges or numerical comparisons.

This is a fundamental skill in API design and directly supports the Infilect job description's requirement to "design and develop REST APIs for frontend and backend." A well-designed API doesn't just expose data; it provides powerful tools for clients to interact with that data efficiently.

1. The Three Pillars of Query Customization

Django REST Framework (DRF) provides a powerful and declarative way to add these features through a system called filter backends. We will focus on three key backends:

  1. DjangoFilterBackend: For granular, field-level filtering based on exact matches (e.g., ?completed=true).
  2. SearchFilter: For text-based, partial-match searching across multiple fields (e.g., ?search=meeting).
  3. OrderingFilter: For allowing the client to specify the sort order of the results (e.g., ?ordering=-priority).

Let's integrate these into our TaskMaster project one by one.


2. Basic Filtering with DjangoFilterBackend

The most common requirement is to filter a list by the value of a specific field. To do this, we'll use the popular django-filter library, which integrates seamlessly with DRF.

The video "Filtering, Searching, and Ordering" by Cryce Truly provides a clear, step-by-step walkthrough of this setup.

Filtering, Searching, and Ordering. Django Rest Framework complete tutorial.#12

This video covers the initial setup for filtering, from installation to implementation in a view.

First, watch the section on installing the library. Then, follow along with the section on configuring the view to see how the filter_backends and filterset_fields attributes are used.

Your Task: Implement Basic Filtering

  1. Install the library. Stop your development server and run:

    pip install django-filter
    
  2. Add it to INSTALLED_APPS. In your myproject/settings.py file, add 'django_filters' to the INSTALLED_APPS list.

    
    
    
    # myproject/settings.py
    INSTALLED_APPS = [
    
    
    
        # ... other apps
        'rest_framework',
        'rest_framework_simplejwt',
        'tasks',
        'django_filters', # Add this line
    ]
    
  3. Configure your TaskViewSet. Open tasks/views.py and modify it to use the DjangoFilterBackend. We'll allow filtering on the completed status and priority.

    
    
    
    # tasks/views.py
    from rest_framework import viewsets
    from rest_framework.permissions import IsAuthenticated
    from django_filters.rest_framework import DjangoFilterBackend # Import this
    
    from .models import Task
    from .serializers import TaskSerializer
    from .pagination import CustomTaskPagination
    
    class TaskViewSet(viewsets.ModelViewSet):
        queryset = Task.objects.all()
        serializer_class = TaskSerializer
        permission_classes = [IsAuthenticated]
        pagination_class = CustomTaskPagination
    
    
    
    
        # Add these lines for filtering
        filter_backends = [DjangoFilterBackend]
        filterset_fields = ['completed', 'priority']
    

    Note: The video and some older tutorials use filter_fields. The modern and correct attribute name used by django-filter is filterset_fields.

Now, restart your server. You can make authenticated requests to your API with new query parameters:

  • http://127.0.0.1:8000/api/tasks/?completed=true will show only completed tasks.
  • http://127.0.0.1:8000/api/tasks/?priority=H will show only high-priority tasks.

3. Adding Text-Based Search

Filtering by exact matches is great, but what if a user wants to find a task by searching for a word in its title or description? That's where SearchFilter comes in.

Filtering, Searching, and Ordering. Django Rest Framework complete tutorial.#12

The same video continues to demonstrate how to add the SearchFilter.

Watch the segment from . It shows how to add SearchFilter to the filter_backends list and define which fields the search should apply to using search_fields.

Your Task: Implement Search

Let's update tasks/views.py to enable searching on the title and description fields.




# tasks/views.py
from rest_framework import viewsets, filters # Import filters
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend




# ... (other imports)

class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    permission_classes = [IsAuthenticated]
    pagination_class = CustomTaskPagination




    # Update filter_backends and add search_fields
    filter_backends = [
        DjangoFilterBackend,
        filters.SearchFilter, # Add SearchFilter
    ]
    filterset_fields = ['completed', 'priority']
    search_fields = ['title', 'description'] # Define searchable fields

Note: Make sure to import filters from rest_framework.

With this change, you can now make requests like:
http://127.0.0.1:8000/api/tasks/?search=monthly%20report

DRF will automatically generate a database query that looks for "monthly" or "report" in the title and description fields of your tasks.


4. Allowing Client-Side Ordering

Finally, let's give the client the ability to sort the results. This is handled by the OrderingFilter.

Filtering, Searching, and Ordering. Django Rest Framework complete tutorial.#12

The video concludes by explaining how to add the OrderingFilter.

Watch the final segment from . It follows the same pattern: add OrderingFilter to the backends and define the fields the client can order by.

Your Task: Implement Ordering

Let's allow clients to order tasks by their creation date (created_at) and priority.




# tasks/views.py




# ... (imports are the same)

class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    permission_classes = [IsAuthenticated]
    pagination_class = CustomTaskPagination

    filter_backends = [
        DjangoFilterBackend,
        filters.SearchFilter,
        filters.OrderingFilter, # Add OrderingFilter
    ]
    filterset_fields = ['completed', 'priority']
    search_fields = ['title', 'description']
    ordering_fields = ['created_at', 'priority'] # Define orderable fields

Now you can control the sort order:

  • http://127.0.0.1:8000/api/tasks/?ordering=priority: Orders by priority (ascending).
  • http://127.0.0.1:8000/api/tasks/?ordering=-created_at: Orders by creation date, newest first (the - indicates descending order).

5. Advanced Filtering with a Custom FilterSet

The methods above cover 80% of use cases. But what if you need more complex filtering, like finding tasks with a priority greater than 'Medium', or tasks created within a specific date range? filterset_fields only does exact matching.

For this, we need to create a custom FilterSet class. This is a more powerful and explicit way to define your filtering logic, and it's a hallmark of a well-structured Django project.

When you define a custom FilterSet with more advanced lookups (like date ranges or number comparisons), the DRF browsable API automatically generates a rich filtering interface for you, making your API much easier to test and explore.

An example of the powerful filtering interface DRF can generate in the browsable API when you use a custom `FilterSet`. This allows for complex queries like date ranges and numerical comparisons directly from the browser.

Let's build one for our Task model. The article "How to Build REST APIs with Django REST Framework" provides a concise example of creating and using a custom filter class.

How to Build REST APIs with Django REST Framework

This article demonstrates the pattern for creating a dedicated filters.py file and a custom FilterSet class, then hooking it into a ViewSet.

Focus on the two code blocks under "Pagination and Filtering". The first block, in a file named books/filters.py, shows how to define a custom FilterSet class with lookups like gte (greater than or equal to) and icontains. The second block shows how to use this custom class in the ViewSet by setting the filterset_class attribute.

Your Task: Create a TaskFilter Class

  1. Create a new file: In your tasks app directory, create a file named filters.py.

  2. Define the TaskFilter class: In tasks/filters.py, define your custom filters. We'll create filters for creation date ranges.

    
    
    
    # tasks/filters.py
    import django_filters
    from .models import Task
    
    class TaskFilter(django_filters.FilterSet):
    
    
    
        # We use CharFilter for title to do a case-insensitive partial match
        title = django_filters.CharFilter(lookup_expr='icontains')
    
    
    
    
        # We use DateFilter for date-based lookups
        created_at_after = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')
        created_at_before = django_filters.DateFilter(field_name='created_at', lookup_expr='lte')
    
        class Meta:
            model = Task
    
    
    
            # Define fields for exact matching here
            fields = ['completed', 'priority']
    
  3. Update TaskViewSet: Go back to tasks/views.py. Remove filterset_fields and replace it with filterset_class.

    
    
    
    # tasks/views.py
    from .filters import TaskFilter # Import the new class
    
    
    
    
    # ... (other imports)
    
    class TaskViewSet(viewsets.ModelViewSet):
    
    
    
        # ... (queryset, serializer_class, etc. remain the same)
        
        filter_backends = [
            DjangoFilterBackend,
            filters.SearchFilter,
            filters.OrderingFilter,
        ]
    
    
    
        # Replace filterset_fields with filterset_class
        filterset_class = TaskFilter
        search_fields = ['title', 'description']
        ordering_fields = ['created_at', 'priority']
    

Now your API is even more powerful! You can perform queries like:

  • http://127.0.0.1:8000/api/tasks/?created_at_after=2023-10-26: Get all tasks created on or after today's date.
  • http://127.0.0.1:8000/api/tasks/?title=review: Get all tasks with "review" in the title (case-insensitive).

By using filterset_class, you've created a more maintainable and extensible system for filtering.


Conclusion

Congratulations! You have transformed your simple CRUD API into a flexible and powerful data service. Clients can now paginate, filter, search, and order data, giving them precise control over the information they retrieve. This dramatically reduces wasted bandwidth and processing time on both the client and server.

Key Takeaways:

  • DRF's filter backends (DjangoFilterBackend, SearchFilter, OrderingFilter) provide a declarative way to add query capabilities to your API.
  • Simple filtering is enabled by installing django-filter and using the filterset_fields, search_fields, and ordering_fields attributes in your view.
  • For advanced logic like date ranges or numerical comparisons, creating a custom FilterSet class and assigning it to filterset_class is the more powerful and professional approach.

Your API is now functional, secure, paginated, and filterable. But how will other developers know how to use it? In the next lesson, we will address this by learning how to automatically generate interactive API documentation using Swagger/OpenAPI, a crucial step for team collaboration and API adoption.