Para criar uma API com Django REST Framework (DRF), instale Django e DRF, modele o recurso, crie um serializer, exponha um ModelViewSet e registre-o em um router. Esse fluxo entrega operações CRUD e uma interface navegável com pouco código, mas uma API pronta para produção também exige validação, autenticação, permissões, paginação, testes e respostas de erro consistentes.

Este guia constrói uma API executável de tarefas. O exemplo usa SQLite localmente e autenticação por token, mantendo as responsabilidades claras para que banco ou autenticação possam evoluir depois. Se o framework ainda for novo para você, consulte primeiro o guia completo de Django.

Ambiente e projeto

Crie um ambiente isolado, instale as dependências e inicie o projeto:

python -m venv .venv
## Linux/macOS: source .venv/bin/activate
## Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install Django djangorestframework
django-admin startproject config .
python manage.py startapp tasks

Ambientes virtuais evitam colisões; o guia de venv em Python explica o isolamento. Em config/settings.py, registre aplicações e configure defaults:

INSTALLED_APPS += [  # Preserva as aplicações geradas pelo Django.
    "rest_framework",
    "rest_framework.authtoken",
    "tasks",
]

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.TokenAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",
    ],
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 20,
}

Defaults seguros evitam publicar acidentalmente uma view nova. Autenticação identifica o usuário; permissão decide se ele pode executar a ação. Elas não são equivalentes.

Modelo e migrações

Em tasks/models.py, represente propriedade explicitamente:

from django.conf import settings
from django.db import models


class Task(models.Model):
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="tasks",
    )
    title = models.CharField(max_length=200)
    completed = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self) -> str:
        return self.title

Execute python manage.py makemigrations e python manage.py migrate. O relacionamento com owner é uma barreira contra vazamento entre contas, desde que a consulta seja filtrada. Para produção, considere PostgreSQL com Python e índices orientados às consultas reais.

Serializer e validação

O serializer converte modelos para tipos renderizáveis, valida entrada e controla campos graváveis. Crie tasks/serializers.py:

from rest_framework import serializers

from .models import Task


class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ["id", "title", "completed", "created_at"]
        read_only_fields = ["id", "created_at"]

    def validate_title(self, value: str) -> str:
        title = value.strip()
        if len(title) < 3:
            raise serializers.ValidationError(
                "O título deve ter pelo menos 3 caracteres."
            )
        return title

Não exponha owner como campo gravável: o servidor o obtém de request.user. Também não use fields = "__all__" por conveniência, pois um campo sensível adicionado ao modelo pode aparecer na API sem revisão. Campos explícitos funcionam como documentação e como uma lista segura do contrato público.

ViewSet, isolamento e rotas

Crie tasks/views.py:

from rest_framework import viewsets

from .models import Task
from .serializers import TaskSerializer


class TaskViewSet(viewsets.ModelViewSet):
    serializer_class = TaskSerializer

    def get_queryset(self):
        return Task.objects.filter(owner=self.request.user)

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

get_queryset() impede listar e acessar objetos de outro usuário, inclusive pela URL de detalhe. Filtrar apenas a listagem seria uma falha de autorização por objeto. Registre as rotas em config/urls.py:

from django.contrib import admin
from django.urls import include, path
from rest_framework.authtoken.views import obtain_auth_token
from rest_framework.routers import DefaultRouter

from tasks.views import TaskViewSet

router = DefaultRouter()
router.register("tasks", TaskViewSet, basename="task")

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include(router.urls)),
    path("api/token/", obtain_auth_token, name="api-token"),
]

O router cria GET/POST /api/tasks/ e GET/PUT/PATCH/DELETE /api/tasks/{id}/. Ele também mantém nomes de URL consistentes, importantes para testes e clientes que resolvem rotas dinamicamente.

Autenticar e testar manualmente

Crie um usuário com python manage.py createsuperuser, inicie python manage.py runserver e obtenha um token:

curl -X POST http://127.0.0.1:8000/api/token/ \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"senha-local"}'

curl -X POST http://127.0.0.1:8000/api/tasks/ \
  -H "Authorization: Token SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Revisar a API"}'

No PowerShell, chame curl.exe explicitamente e use a crase para continuar a linha:

curl.exe -X POST http://127.0.0.1:8000/api/token/ `
  -H "Content-Type: application/json" `
  -d '{"username":"admin","password":"senha-local"}'

curl.exe -X POST http://127.0.0.1:8000/api/tasks/ `
  -H "Authorization: Token SEU_TOKEN" `
  -H "Content-Type: application/json" `
  -d '{"title":"Revisar a API"}'

Use credenciais apenas locais no exemplo. Tokens devem trafegar por HTTPS, permanecer fora de logs e repositórios e poder ser revogados. A autenticação por token nativa é simples; requisitos de expiração, rotação ou clientes terceiros podem justificar OAuth2 ou outro mecanismo. A documentação de autenticação do DRF compara as opções.

Testes da API

Testes comprovam autenticação, validação e isolamento. Em tasks/tests.py:

from django.contrib.auth import get_user_model
from rest_framework import status
from rest_framework.test import APITestCase

from .models import Task


class TaskApiTests(APITestCase):
    def setUp(self):
        self.user = get_user_model().objects.create_user(
            username="ana", password="senha-forte-local"
        )
        self.client.force_authenticate(self.user)

    def test_create_task_assigns_owner(self):
        response = self.client.post("/api/tasks/", {"title": "Testar endpoint"})
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertTrue(Task.objects.filter(owner=self.user).exists())

    def test_rejects_short_title(self):
        response = self.client.post("/api/tasks/", {"title": "x"})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

Execute python manage.py test. Adicione casos sem credencial, acesso ao ID de outra conta, paginação e métodos proibidos. O guia de testes com pytest ajuda se o projeto preferir pytest.

Erros, desempenho e práticas de produção

O DRF transforma ValidationError em resposta 400 estruturada. Não capture Exception para sempre devolver 200, nem exponha traceback ao cliente. Retorne 401 para autenticação ausente ou inválida, 403 quando a identidade não tem permissão, 404 para recurso invisível e 400 para dados inválidos. Registre um identificador de correlação e detalhes internos, sem token ou senha; veja logging em Python.

Evite N+1 usando select_related() e prefetch_related() quando serializers atravessarem relações. Paginação limita custo, mas também defina throttling, limites no proxy e timeouts. Versione contratos quando houver consumidores externos. Use CORS somente para origens necessárias; CORS não substitui autenticação. Mantenha DEBUG=False, SECRET_KEY em variável de ambiente, ALLOWED_HOSTS restrito e HTTPS no deploy. As recomendações de implantação do Django oferecem uma lista de verificação.

Fontes oficiais

Perguntas frequentes

Serializer é o mesmo que formulário Django?

Não. Ambos validam dados, mas serializers representam recursos de API, negociam formatos e criam ou atualizam objetos a partir de payloads.

ViewSet ou APIView: qual escolher?

Use ModelViewSet para CRUD convencional. Escolha APIView quando o endpoint representar uma operação que não se encaixa naturalmente em um recurso CRUD.

TokenAuthentication é suficiente para produção?

Pode ser em sistemas simples se houver HTTPS, revogação e armazenamento seguro. Expiração, rotação e escopos podem exigir uma solução mais completa.

Como impedir que um usuário veja dados de outro?

Filtre sempre o queryset pela identidade e teste listagem e detalhe. Para regras adicionais, implemente permissões por objeto sem substituir esse isolamento básico.

Conclusão

Uma API DRF sólida nasce de limites explícitos: serializer controla entrada e saída, queryset restringe propriedade, autenticação identifica o cliente e permissões autorizam ações. O CRUD curto é apenas o começo. Com defaults fechados, validação, paginação, testes de isolamento, erros coerentes e configuração segura de produção, o mesmo projeto pode evoluir sem transformar conveniência em exposição de dados.