Django REST Framework: serializers and views
So far Nidaan renders HTML pages. But a clinic often needs more: a mobile app for patients, a separate JavaScript frontend, or an integration with a lab's system — all of which want data, as JSON, not HTML. That is an API, and in the Django world the standard tool is Django REST Framework (DRF). This lesson introduces DRF's two core pieces — serializers and API views — and gets a working JSON endpoint returning Nidaan's patients.
What DRF adds, and why not just JsonResponse
You saw JsonResponse in the views lesson — you could build an API by hand, serialising models to dicts
and parsing request bodies yourself. For anything beyond a trivial endpoint, you should not, for the same
reason you use forms instead of reading request.POST: you would re-implement serialisation, validation,
content negotiation, authentication, pagination and error formatting — all of which DRF provides, correctly
and consistently. DRF is to APIs what Django's forms and generic views are to HTML pages: a batteries-
included layer over the tedious, error-prone parts. Install it and add it to INSTALLED_APPS:
INSTALLED_APPS = [ ..., "rest_framework" ]
Serializers: models to JSON, and back with validation
The heart of DRF is the serializer. It does two jobs: convert model instances to JSON
(serialisation), and validate incoming JSON and convert it back to model data (deserialisation). A
ModelSerializer builds this from a model, exactly as ModelForm builds a form:
# patients/serializers.py
from rest_framework import serializers
from .models import Patient
class PatientSerializer(serializers.ModelSerializer):
class Meta:
model = Patient
fields = ["id", "name", "phone", "city"] # explicit fields, never "__all__"
def validate_phone(self, value):
digits = value.lstrip("+").replace(" ", "")
if not digits.isdigit() or len(digits) < 10:
raise serializers.ValidationError("Enter a valid phone number.")
return value
This is deliberately parallel to the ModelForm you already wrote: explicit fields (never "__all__" on
an API — it leaks fields), and a validate_<field> method for custom rules (mirroring clean_<field>).
Verified: posting phone="12" returns the error {'phone': ['Enter a valid phone number.']}. The
serializer is your API's contract — the fields it lists are what the API exposes and accepts, and its
validation is what protects you from bad input. Everything you learnt about forms and validation transfers
directly.
Fields you compute or rename
Serializers can expose more than raw model fields — a related object's value, a computed field, a renamed field:
class AppointmentSerializer(serializers.ModelSerializer):
patient_name = serializers.CharField(source="patient.name", read_only=True)
class Meta:
model = Appointment
fields = ["id", "patient", "patient_name", "doctor", "scheduled_for", "status", "fee"]
patient_name pulls the patient's name across the relationship with source="patient.name", read_only
so it is output but not required on input. This is how you shape the JSON your clients actually want — a
mobile app showing an appointment wants the patient's name, not just their id, and the serializer provides
both without extra queries in the client. Declared serializer fields (CharField, IntegerField, etc.)
work like form fields.
API views: from a serializer to an endpoint
A serializer describes the data; a view exposes it over HTTP. DRF offers a spectrum, from most explicit to most concise:
APIView— the DRF equivalent of a function/class view: you writeget,postmethods returning DRFResponseobjects. Most control, most code.- Generic views (
ListCreateAPIView,RetrieveUpdateDestroyAPIView) — like Django's generic CBVs, for standard patterns. ViewSet— bundles all the CRUD actions for a resource into one class (the next lesson). Least code for standard REST.
A minimal APIView to see the mechanics:
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Patient
from .serializers import PatientSerializer
class PatientListAPI(APIView):
def get(self, request):
patients = Patient.objects.all()
serializer = PatientSerializer(patients, many=True) # many=True for a queryset
return Response(serializer.data) # DRF Response, not HttpResponse
PatientSerializer(patients, many=True) serialises a whole queryset (many=True); .data is the
JSON-ready result; DRF's Response handles turning it into a proper JSON HTTP response with the right
headers. Verified against the ViewSet form (next lesson): the list endpoint returns HTTP 200 with the
patients as JSON. For a single object you drop many=True; for creating, you pass data=request.data, call
serializer.is_valid(), and serializer.save() — again mirroring forms.
The browsable API — a free gift
Point your browser (not a JSON client) at a DRF endpoint in development and you get the browsable API: a rendered HTML interface showing the data, the allowed methods, and forms to try requests. It is generated automatically and is superb for exploring and testing an API by hand while you build it. It disappears appropriately when a client requests JSON — DRF does content negotiation, returning HTML to a browser and JSON to an API client from the same endpoint. This alone makes developing a DRF API markedly more pleasant than hand-rolled JSON views.
Check your work
Why DRF over JsonResponse. It provides serialisation, validation, auth, pagination, content
negotiation and consistent errors — the API equivalent of using forms/generic views instead of hand-rolling.
What a serializer does. Converts models to JSON and validates/converts incoming JSON back —
ModelSerializer builds it from a model, parallel to ModelForm. Explicit fields, validate_<field> for
custom rules (verified: bad phone → {'phone': [...]}).
Computed/renamed fields. serializers.CharField(source="patient.name", read_only=True) exposes a
related or renamed value — shaping the JSON clients want.
The view spectrum. APIView (most control) → generic API views → ViewSet (least code) — pick by how
standard the endpoint is.
Serialising in a view. Serializer(queryset, many=True).data for a list; return a DRF Response;
create with data=request.data + is_valid() + save().
The browsable API. DRF auto-renders an HTML interface for endpoints in the browser (content-negotiated against JSON clients) — invaluable for exploring an API by hand.
Practice
- Install DRF, add it to
INSTALLED_APPS, and writePatientSerializerwith explicitfields. - Serialise a queryset in the shell:
PatientSerializer(Patient.objects.all(), many=True).data; inspect the JSON-ready output. - Add a
validate_phonemethod; deserialise{"phone": "12", ...}and confirmis_valid()is False with the error. - Add a computed/renamed field with
source=andread_only=True; confirm it appears in the output. - Write a
PatientListAPI(APIView)with aget; visit it in a browser and explore the browsable API, then request it as JSON. - Add a
postto the same view that validates and saves; create a patient through it.
Official documentation
- Django REST Framework — Tutorial — Serializers and views from scratch.
- DRF — Serializers — The full serializer reference.
- DRF — The browsable API — The auto-generated HTML interface.
Next: ViewSets, routers and pagination.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship