Olá, desenvolvedores! 👋
Se você está construindo APIs REST, aplicações web complexas ou sistemas que precisam escalar rapidamente, provavelmente já ouviu falar do Django. Talvez você esteja vindo do ecossistema JavaScript (Node.js, Express, Next.js) e se perguntando: "Por que eu deveria considerar Python e Django?"
Hoje vou mostrar não apenas por que o Django é relevante, mas por que ele é superior em diversos cenários. Vamos explorar código real, comparações práticas e os recursos que tornam o Django uma máquina de produtividade.
O Que É Django?
Django é um framework web de alto nível escrito em Python que incentiva o desenvolvimento rápido e design limpo e pragmático. Criado em 2005, ele segue o princípio "Batteries-Included" (Baterias Inclusas) - tudo o que você precisa já vem na caixa.
Por Que Isso Importa
Antes de mergulharmos no código, vamos entender o problema fundamental:
// ❌ Ecossistema JavaScript - Stack Overflow de Decisões
// Para construir uma API completa, você precisa escolher e configurar:
const express = require('express'); // Framework básico
const helmet = require('helmet'); // Segurança
const cors = require('cors'); // CORS
const morgan = require('morgan'); // Logging
const { Sequelize } = require('sequelize'); // ORM
const bcrypt = require('bcrypt'); // Hash de senhas
const jwt = require('jsonwebtoken'); // Autenticação
const joi = require('joi'); // Validação
const multer = require('multer'); // Upload de arquivos
// ... e mais 20+ pacotes para funcionalidades básicas
// Depois de instalar tudo, você ainda precisa:
// 1. Configurar cada pacote individualmente
// 2. Garantir compatibilidade entre versões
// 3. Implementar segurança manualmente
// 4. Criar toda estrutura de usuários, permissões, admin...
// 5. Escrever migrations manualmente ou configurar ferramenta# ✅ Django - Pronto Para Produção em Minutos
# pip install django djangorestframework
# Você já tem:
# - ORM poderoso integrado
# - Sistema de autenticação completo
# - Admin panel automático
# - Sistema de migrations
# - Proteção CSRF/XSS/SQL Injection
# - Framework de forms e validação
# - Sistema de templates
# - Middleware de segurança
# - Cache framework
# - Sistema de sinais (signals)
# - Framework de testes
# E MUITO mais...A diferença é gritante. Com Django, você foca na lógica de negócio desde o dia 1.
Quando Você Deve Usar Django?
Casos de uso ideais:
APIs REST complexas com múltiplos recursos e relacionamentos
Aplicações CRUD que precisam de um admin panel robusto
Sistemas multi-tenant com isolamento de dados por cliente
Projetos que precisam escalar rapidamente do MVP para produção
Aplicações que exigem segurança rigorosa (fintech, healthcare, gov)
Quando NÃO usar Django:
Microserviços extremamente leves onde cada milissegundo conta (considere FastAPI ou Go)
Aplicações real-time intensivas como jogos multiplayer (WebSockets não são o forte nativo)
Serverless functions com cold start crítico (melhor usar Node.js ou Python puro)
Projetos experimentais onde a flexibilidade total é mais importante que convenção
Configurando Seu Primeiro Projeto Django
Vamos construir isso passo a passo, explicando cada decisão.
Passo 1: Instalação e Setup
Opção 1: Usando Poetry (Recomendado)
# Instalar Poetry (gerenciador de dependências moderno)
curl -sSL https://install.python-poetry.org | python3 -
# Criar novo projeto
poetry new meu_projeto
cd meu_projeto
# Adicionar Django
poetry add django djangorestframework python-decouple psycopg2-binary
# Criar projeto Django
poetry run django-admin startproject config .Opção 2: Usando pip e venv (Tradicional)
# Criar ambiente virtual
python -m venv venv
source venv/bin/activate # No Windows: venv\Scripts\activate
# Instalar Django
pip install django djangorestframework python-decouple psycopg2-binary
# Criar projeto
django-admin startproject config .Estrutura de pastas criada:
meu_projeto/
├── config/ # Configurações principais
│ ├── __init__.py
│ ├── settings.py # Todas as configurações do projeto
│ ├── urls.py # Roteamento principal
│ ├── asgi.py # Para deploy assíncrono
│ └── wsgi.py # Para deploy tradicional
├── manage.py # CLI do Django (seu melhor amigo)
└── db.sqlite3 # Banco de dados (será criado)Passo 2: Configuração Inicial Segura
Vamos configurar o projeto seguindo as melhores práticas desde o início:
# config/settings.py
import os
from pathlib import Path
from decouple import config # Para variáveis de ambiente
BASE_DIR = Path(__file__).resolve().parent.parent
# NUNCA comite SECRET_KEY em produção
SECRET_KEY = config('SECRET_KEY', default='dev-key-change-in-production')
# DEBUG deve ser False em produção
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1').split(',')
# Aplicações instaladas
INSTALLED_APPS = [
'django.contrib.admin', # Admin panel automático
'django.contrib.auth', # Sistema de autenticação
'django.contrib.contenttypes', # Sistema de tipos de conteúdo
'django.contrib.sessions', # Gerenciamento de sessões
'django.contrib.messages', # Framework de mensagens
'django.contrib.staticfiles', # Gerenciamento de arquivos estáticos
'rest_framework', # Django REST Framework
'rest_framework.authtoken', # Autenticação via token
# Suas apps aqui
]
# Middleware - a ordem importa!
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # Segurança
'django.contrib.sessions.middleware.SessionMiddleware', # Sessões
'django.middleware.common.CommonMiddleware', # Comum
'django.middleware.csrf.CsrfViewMiddleware', # Proteção CSRF
'django.contrib.auth.middleware.AuthenticationMiddleware', # Auth
'django.contrib.messages.middleware.MessageMiddleware', # Mensagens
'django.middleware.clickjacking.XFrameOptionsMiddleware', # Clickjacking
]
# Banco de dados - PostgreSQL em produção
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME', default='meu_projeto'),
'USER': config('DB_USER', default='postgres'),
'PASSWORD': config('DB_PASSWORD', default=''),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='5432'),
}
}
# REST Framework Configuration
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,
}Por que essa configuração é tão boa:
Segurança desde o início: Usa variáveis de ambiente para dados sensíveis
Middleware protege automaticamente: CSRF, XSS, Clickjacking já configurados
PostgreSQL pronto: O banco de dados mais robusto para produção
REST Framework integrado: API pronta para começar
Passo 3: Criando Sua Primeira App
Django organiza código em "apps" - módulos reutilizáveis. Vamos criar uma API de tarefas:
# Criar app de tarefas
python manage.py startapp tasks
# Estrutura criada:
# tasks/
# ├── __init__.py
# ├── admin.py # Configuração do admin panel
# ├── apps.py # Configuração da app
# ├── models.py # Modelos de dados (ORM)
# ├── tests.py # Testes
# ├── views.py # Views (lógica de negócio)
# └── migrations/ # Migrations do banco de dadosAgora registre a app em settings.py:
INSTALLED_APPS = [
# ... apps Django padrão
'rest_framework',
'tasks', # Nossa nova app
]O ORM do Django: Poder em Python Puro
O ORM (Object-Relational Mapper) do Django é uma das suas maiores forças. Vamos construir modelos progressivamente.
3.1: Modelo Básico de Task
# tasks/models.py
from django.db import models
from django.contrib.auth.models import User
class Task(models.Model):
"""
Modelo básico de tarefa.
Cada campo se torna uma coluna no banco de dados automaticamente.
"""
# Campos básicos
title = models.CharField(
max_length=200,
help_text="Título da tarefa"
)
description = models.TextField(
blank=True, # Pode ser vazio
help_text="Descrição detalhada da tarefa"
)
completed = models.BooleanField(
default=False,
help_text="Tarefa concluída?"
)
# Relacionamento com usuário (Foreign Key)
created_by = models.ForeignKey(
User,
on_delete=models.CASCADE, # Se usuário deletado, deleta tasks
related_name='tasks' # User.tasks.all() retorna todas as tasks
)
# Timestamps automáticos
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at'] # Mais recentes primeiro
verbose_name = 'Tarefa'
verbose_name_plural = 'Tarefas'
def __str__(self):
"""Representação em string do modelo"""
return f"{self.title} - {'✓' if self.completed else '✗'}"Agora crie e aplique a migration:
# Criar migration (Django analisa seu modelo e cria SQL)
python manage.py makemigrations
# Aplicar migration (Django executa o SQL)
python manage.py migratePor que isso é poderoso:
Zero SQL escrito: Django gerou todo o SQL necessário
Type-safe: Python analisa os tipos em tempo de desenvolvimento
Relacionamentos simples: Foreign Keys são triviais de definir
Migrations versionadas: Controle de versão do schema do banco
3.2: Adicionando Relacionamentos Complexos
# tasks/models.py
class Project(models.Model):
"""Projeto que contém múltiplas tarefas"""
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
owner = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='owned_projects'
)
members = models.ManyToManyField(
User,
related_name='projects',
blank=True
)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Task(models.Model):
# ... campos anteriores ...
# Adicionar relacionamento com Project
project = models.ForeignKey(
Project,
on_delete=models.CASCADE,
related_name='tasks',
null=True, # Tarefas podem não ter projeto
blank=True
)
# Tags usando ManyToMany
tags = models.ManyToManyField(
'Tag', # String reference (lazy)
related_name='tasks',
blank=True
)
# Choices (enums)
PRIORITY_CHOICES = [
('low', 'Baixa'),
('medium', 'Média'),
('high', 'Alta'),
]
priority = models.CharField(
max_length=10,
choices=PRIORITY_CHOICES,
default='medium'
)
class Tag(models.Model):
"""Tags para categorizar tarefas"""
name = models.CharField(max_length=50, unique=True)
color = models.CharField(max_length=7, default='#000000') # Hex color
def __str__(self):
return self.nameDiferenças importantes:
ForeignKey (1-para-N): Um projeto tem muitas tasks, cada task tem um projeto
ManyToManyField (N-para-N): Uma task pode ter várias tags, uma tag pode estar em várias tasks
Choices: Implementa enums de forma elegante
related_name: Permite navegação reversa (project.tasks.all())
3.3: Queries Poderosas com o ORM
Agora que temos modelos, vamos ver como consultá-los:
# Django shell: python manage.py shell
from tasks.models import Task, Project, Tag
from django.contrib.auth.models import User
# ✅ CRUD Básico
# Criar
task = Task.objects.create(
title="Implementar API",
description="API REST com Django",
created_by=User.objects.first(),
priority='high'
)
# Ler (queries)
all_tasks = Task.objects.all()
high_priority = Task.objects.filter(priority='high')
completed = Task.objects.filter(completed=True)
my_tasks = Task.objects.filter(created_by=request.user)
# Atualizar
task.completed = True
task.save()
# Ou atualizar em massa
Task.objects.filter(priority='low').update(completed=True)
# Deletar
task.delete()
# ✅ Queries Complexas
# Busca por texto
tasks = Task.objects.filter(title__icontains='api') # Case-insensitive
# Relacionamentos (JOINs automáticos)
tasks_in_project = Task.objects.filter(project__name='Projeto Alpha')
# Múltiplos filtros (AND)
urgent = Task.objects.filter(
priority='high',
completed=False,
created_at__gte=timezone.now() - timedelta(days=7)
)
# OR queries
from django.db.models import Q
tasks = Task.objects.filter(
Q(priority='high') | Q(tags__name='urgente')
)
# ✅ Agregações e Anotações
from django.db.models import Count, Avg
# Contar tasks por usuário
user_stats = User.objects.annotate(
total_tasks=Count('tasks'),
completed_tasks=Count('tasks', filter=Q(tasks__completed=True))
)
# Projetos com mais de 10 tasks
popular_projects = Project.objects.annotate(
task_count=Count('tasks')
).filter(task_count__gt=10)
# ✅ Prefetch e Select Related (Otimização)
# ❌ N+1 problem (1 query + N queries)
tasks = Task.objects.all()
for task in tasks:
print(task.created_by.username) # Query para cada task!
# ✅ Select Related (1 query com JOIN)
tasks = Task.objects.select_related('created_by', 'project').all()
for task in tasks:
print(task.created_by.username) # Sem queries adicionais!
# ✅ Prefetch Related (para ManyToMany)
tasks = Task.objects.prefetch_related('tags').all()
for task in tasks:
print(task.tags.all()) # Apenas 2 queries total!Por que o ORM do Django é excepcional:
Pythônico: Queries parecem Python natural
Type-safe: IDE pode autocompletar e verificar tipos
Otimização fácil: select_related e prefetch_related resolvem N+1
Complexidade gerenciável: Queries SQL complexas ficam legíveis
Criando Uma API REST Completa
Vamos construir uma API REST usando Django REST Framework (DRF).
4.1: Serializers - Transformando Modelos em JSON
# tasks/serializers.py
from rest_framework import serializers
from .models import Task, Project, Tag
from django.contrib.auth.models import User
class TagSerializer(serializers.ModelSerializer):
"""Serializer para Tags"""
class Meta:
model = Tag
fields = ['id', 'name', 'color']
class UserSerializer(serializers.ModelSerializer):
"""Serializer básico de usuário"""
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name']
read_only_fields = ['id']
class TaskSerializer(serializers.ModelSerializer):
"""
Serializer completo para Tasks.
Inclui relacionamentos e campos calculados.
"""
# Nested serializers (read-only)
created_by = UserSerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
# Write-only fields
tag_ids = serializers.ListField(
child=serializers.IntegerField(),
write_only=True,
required=False
)
# Campos calculados (SerializerMethodField)
days_since_creation = serializers.SerializerMethodField()
is_overdue = serializers.SerializerMethodField()
class Meta:
model = Task
fields = [
'id', 'title', 'description', 'completed',
'priority', 'created_by', 'project', 'tags',
'tag_ids', 'created_at', 'updated_at',
'days_since_creation', 'is_overdue'
]
read_only_fields = ['id', 'created_at', 'updated_at', 'created_by']
def get_days_since_creation(self, obj):
"""Calcula dias desde criação"""
from django.utils import timezone
delta = timezone.now() - obj.created_at
return delta.days
def get_is_overdue(self, obj):
"""Verifica se está atrasada (exemplo: mais de 30 dias)"""
return self.get_days_since_creation(obj) > 30 and not obj.completed
def create(self, validated_data):
"""Sobrescrever create para lidar com tags"""
tag_ids = validated_data.pop('tag_ids', [])
task = Task.objects.create(**validated_data)
if tag_ids:
tags = Tag.objects.filter(id__in=tag_ids)
task.tags.set(tags)
return task
def update(self, instance, validated_data):
"""Sobrescrever update para lidar com tags"""
tag_ids = validated_data.pop('tag_ids', None)
# Atualizar campos normais
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
# Atualizar tags se fornecidas
if tag_ids is not None:
tags = Tag.objects.filter(id__in=tag_ids)
instance.tags.set(tags)
return instance
class ProjectSerializer(serializers.ModelSerializer):
"""Serializer para Projects com tasks aninhadas"""
owner = UserSerializer(read_only=True)
members = UserSerializer(many=True, read_only=True)
tasks = TaskSerializer(many=True, read_only=True)
task_count = serializers.IntegerField(read_only=True)
class Meta:
model = Project
fields = [
'id', 'name', 'description', 'owner',
'members', 'tasks', 'task_count', 'created_at'
]Por que Serializers são poderosos:
Validação automática: DRF valida dados contra o modelo
Nested serializers: Representa relacionamentos facilmente
Campos calculados: SerializerMethodField para lógica customizada
Read/Write separation: Diferentes representações para GET vs POST
4.2: ViewSets - Controllers com Superpoderes
# tasks/views.py
from rest_framework import viewsets, status, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from django.db.models import Count, Q
from .models import Task, Project, Tag
from .serializers import TaskSerializer, ProjectSerializer, TagSerializer
class TaskViewSet(viewsets.ModelViewSet):
"""
ViewSet completo para Tasks.
Fornece automaticamente: list, create, retrieve, update, destroy
"""
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = [IsAuthenticated]
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ['title', 'description']
ordering_fields = ['created_at', 'priority', 'completed']
def get_queryset(self):
"""
Customizar queryset:
- Apenas tasks do usuário logado
- Otimizar com select_related
"""
return Task.objects.filter(
created_by=self.request.user
).select_related('created_by', 'project').prefetch_related('tags')
def perform_create(self, serializer):
"""Automaticamente atribuir usuário logado"""
serializer.save(created_by=self.request.user)
# ✅ Custom Actions - Endpoints customizados
@action(detail=False, methods=['get'])
def completed(self, request):
"""GET /api/tasks/completed/ - Retorna tasks completas"""
tasks = self.get_queryset().filter(completed=True)
serializer = self.get_serializer(tasks, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def high_priority(self, request):
"""GET /api/tasks/high_priority/ - Retorna tasks de alta prioridade"""
tasks = self.get_queryset().filter(
priority='high',
completed=False
)
serializer = self.get_serializer(tasks, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def complete(self, request, pk=None):
"""POST /api/tasks/{id}/complete/ - Marca task como completa"""
task = self.get_object()
task.completed = True
task.save()
serializer = self.get_serializer(task)
return Response(serializer.data)
@action(detail=False, methods=['post'])
def bulk_update(self, request):
"""POST /api/tasks/bulk_update/ - Atualização em massa"""
task_ids = request.data.get('task_ids', [])
updates = request.data.get('updates', {})
if not task_ids:
return Response(
{'error': 'task_ids é obrigatório'},
status=status.HTTP_400_BAD_REQUEST
)
tasks = self.get_queryset().filter(id__in=task_ids)
updated_count = tasks.update(**updates)
return Response({
'updated': updated_count,
'message': f'{updated_count} tasks atualizadas'
})
class ProjectViewSet(viewsets.ModelViewSet):
"""ViewSet para Projects com estatísticas"""
queryset = Project.objects.all()
serializer_class = ProjectSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""Apenas projetos onde usuário é owner ou member"""
user = self.request.user
return Project.objects.filter(
Q(owner=user) | Q(members=user)
).annotate(
task_count=Count('tasks')
).distinct().prefetch_related('members', 'tasks')
def perform_create(self, serializer):
"""Owner automático é o usuário logado"""
serializer.save(owner=self.request.user)
@action(detail=True, methods=['post'])
def add_member(self, request, pk=None):
"""POST /api/projects/{id}/add_member/ - Adiciona membro"""
project = self.get_object()
user_id = request.data.get('user_id')
try:
from django.contrib.auth.models import User
user = User.objects.get(id=user_id)
project.members.add(user)
return Response({'message': f'{user.username} adicionado'})
except User.DoesNotExist:
return Response(
{'error': 'Usuário não encontrado'},
status=status.HTTP_404_NOT_FOUND
)
@action(detail=True, methods=['get'])
def statistics(self, request, pk=None):
"""GET /api/projects/{id}/statistics/ - Estatísticas do projeto"""
project = self.get_object()
tasks = project.tasks.all()
stats = {
'total_tasks': tasks.count(),
'completed_tasks': tasks.filter(completed=True).count(),
'high_priority_tasks': tasks.filter(priority='high').count(),
'tasks_by_priority': {
'high': tasks.filter(priority='high').count(),
'medium': tasks.filter(priority='medium').count(),
'low': tasks.filter(priority='low').count(),
}
}
return Response(stats)Por que ViewSets são incríveis:
CRUD automático: list, create, retrieve, update, destroy prontos
Custom actions: @action decorator para endpoints customizados
Filters integrados: Search e ordering out-of-the-box
Permissions: Sistema de permissões granular
4.3: URLs - Conectando Tudo
# tasks/urls.py
from rest_framework.routers import DefaultRouter
from .views import TaskViewSet, ProjectViewSet
router = DefaultRouter()
router.register(r'tasks', TaskViewSet, basename='task')
router.register(r'projects', ProjectViewSet, basename='project')
urlpatterns = router.urls
# Isso gera automaticamente:
# GET /tasks/ - Lista tasks
# POST /tasks/ - Cria task
# GET /tasks/{id}/ - Detalhe da task
# PUT /tasks/{id}/ - Atualiza task (completo)
# PATCH /tasks/{id}/ - Atualiza task (parcial)
# DELETE /tasks/{id}/ - Deleta task
# GET /tasks/completed/ - Custom action
# POST /tasks/{id}/complete/ - Custom action
# ... e todas as rotas de projects# config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('tasks.urls')),
path('api-auth/', include('rest_framework.urls')), # Login UI
]O Admin Panel: A Killer Feature
Registre seus modelos no admin e tenha uma interface completa de gerenciamento:
# tasks/admin.py
from django.contrib import admin
from .models import Task, Project, Tag
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
"""Admin customizado para Tasks"""
list_display = ['title', 'priority', 'completed', 'created_by', 'created_at']
list_filter = ['completed', 'priority', 'created_at']
search_fields = ['title', 'description']
date_hierarchy = 'created_at'
ordering = ['-created_at']
# Campos readonly
readonly_fields = ['created_at', 'updated_at']
# Organização dos campos no form
fieldsets = [
('Informações Básicas', {
'fields': ['title', 'description', 'completed']
}),
('Categorização', {
'fields': ['priority', 'project', 'tags']
}),
('Metadados', {
'fields': ['created_by', 'created_at', 'updated_at'],
'classes': ['collapse'] # Seção colapsável
}),
]
# Ações em massa
actions = ['mark_as_completed', 'mark_as_incomplete']
def mark_as_completed(self, request, queryset):
updated = queryset.update(completed=True)
self.message_user(request, f'{updated} tasks marcadas como completas')
mark_as_completed.short_description = 'Marcar como completas'
def mark_as_incomplete(self, request, queryset):
updated = queryset.update(completed=False)
self.message_user(request, f'{updated} tasks marcadas como incompletas')
mark_as_incomplete.short_description = 'Marcar como incompletas'
@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
list_display = ['name', 'owner', 'task_count', 'created_at']
search_fields = ['name', 'description']
filter_horizontal = ['members'] # Interface melhor para ManyToMany
def task_count(self, obj):
return obj.tasks.count()
task_count.short_description = 'Total de Tasks'
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ['name', 'color']
search_fields = ['name']Acesse http://localhost:8000/admin/ e você tem uma interface completa de gerenciamento!
Segurança: Django vs JavaScript
Proteção CSRF
# ✅ Django - Automático
# Em qualquer formulário HTML:
<form method="post">
{% csrf_token %} <!-- Django injeta token automaticamente -->
<!-- campos do form -->
</form>
# No frontend (React/Vue):
# Django envia cookie CSRF automaticamente
# Basta incluir o header:
headers: {
'X-CSRFToken': getCookie('csrftoken')
}// ❌ Node.js/Express - Manual
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.post('/api/tasks', csrfProtection, (req, res) => {
// Você precisa manualmente:
// 1. Configurar middleware
// 2. Enviar token para frontend
// 3. Validar em cada rota
// 4. Lidar com errors
});Prevenção de SQL Injection
# ✅ Django ORM - Impossível de injetar SQL
# Isso é SEGURO (parametrizado automaticamente)
username = request.GET.get('username')
user = User.objects.filter(username=username).first()
# Até mesmo raw SQL é parametrizado:
user = User.objects.raw(
'SELECT * FROM auth_user WHERE username = %s',
[username] # Django parametriza automaticamente
)[0]// ❌ Node.js - Você precisa ser cuidadoso
// PERIGOSO (vulnerável a SQL injection)
const username = req.query.username;
const user = await db.query(`SELECT * FROM users WHERE username = '${username}'`);
// SEGURO (precisa usar ? manualmente)
const user = await db.query(
'SELECT * FROM users WHERE username = ?',
[username]
);Autenticação e Autorização
Django vem com um sistema completo de auth:
# tasks/permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Permission customizada:
- Leitura: Todos autenticados
- Escrita: Apenas o dono
"""
def has_object_permission(self, request, view, obj):
# Leitura permitida para todos
if request.method in permissions.SAFE_METHODS:
return True
# Escrita apenas para o dono
return obj.created_by == request.user
class IsProjectMember(permissions.BasePermission):
"""Apenas membros do projeto podem ver/editar"""
def has_object_permission(self, request, view, obj):
return (
obj.owner == request.user or
request.user in obj.members.all()
)# Usar nas views:
class TaskViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
# ...Sistema de Grupos e Permissões
# Criar grupos e permissões
from django.contrib.auth.models import Group, Permission
# Criar grupo
managers = Group.objects.create(name='Managers')
# Adicionar permissões
permissions = Permission.objects.filter(
codename__in=['add_task', 'change_task', 'delete_task', 'view_task']
)
managers.permissions.set(permissions)
# Adicionar usuário ao grupo
user.groups.add(managers)
# Verificar permissões
if user.has_perm('tasks.delete_task'):
# Usuário pode deletar tasks
passTarefas Assíncronas com Celery
Para processar tarefas em background (envio de emails, processamento de arquivos, etc):
# tasks/tasks.py
from celery import shared_task
from django.core.mail import send_mail
@shared_task
def send_task_notification(task_id):
"""Envia notificação quando task é criada"""
from .models import Task
task = Task.objects.get(id=task_id)
send_mail(
subject=f'Nova tarefa: {task.title}',
message=task.description,
from_email='noreply@app.com',
recipient_list=[task.created_by.email],
)
return f'Email enviado para {task.created_by.email}'
@shared_task
def generate_project_report(project_id):
"""Gera relatório do projeto em background"""
from .models import Project
import time
project = Project.objects.get(id=project_id)
# Processamento pesado
time.sleep(5) # Simula processamento
report = {
'project': project.name,
'total_tasks': project.tasks.count(),
'completed': project.tasks.filter(completed=True).count(),
}
return report# Chamar a task (não bloqueia)
from .tasks import send_task_notification
def perform_create(self, serializer):
task = serializer.save(created_by=self.request.user)
# Executa em background
send_task_notification.delay(task.id)Testes: Framework Integrado
# tasks/tests.py
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework.test import APITestCase
from rest_framework import status
from .models import Task, Project
class TaskModelTest(TestCase):
"""Testes do modelo Task"""
def setUp(self):
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
def test_task_creation(self):
"""Teste de criação de task"""
task = Task.objects.create(
title='Test Task',
description='Description',
created_by=self.user
)
self.assertEqual(task.title, 'Test Task')
self.assertFalse(task.completed)
self.assertEqual(task.priority, 'medium')
def test_task_str(self):
"""Teste do método __str__"""
task = Task.objects.create(
title='Test',
created_by=self.user
)
self.assertEqual(str(task), 'Test - ✗')
class TaskAPITest(APITestCase):
"""Testes da API de Tasks"""
def setUp(self):
self.user = User.objects.create_user(
username='testuser',
password='testpass123'
)
self.client.force_authenticate(user=self.user)
def test_create_task(self):
"""Teste de criação via API"""
data = {
'title': 'API Test Task',
'description': 'Created via API',
'priority': 'high'
}
response = self.client.post('/api/tasks/', data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Task.objects.count(), 1)
self.assertEqual(Task.objects.first().title, 'API Test Task')
def test_list_tasks(self):
"""Teste de listagem"""
Task.objects.create(
title='Task 1',
created_by=self.user
)
Task.objects.create(
title='Task 2',
created_by=self.user
)
response = self.client.get('/api/tasks/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 2)
def test_complete_task(self):
"""Teste de custom action complete"""
task = Task.objects.create(
title='Task',
created_by=self.user
)
response = self.client.post(f'/api/tasks/{task.id}/complete/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
task.refresh_from_db()
self.assertTrue(task.completed)Execute os testes:
# Rodar todos os testes
python manage.py test
# Rodar testes de uma app específica
python manage.py test tasks
# Rodar com coverage
pip install coverage
coverage run --source='.' manage.py test
coverage reportArmadilhas Comuns e Como Evitá-las
1. N+1 Query Problem
O problema: Fazer queries dentro de loops
# ❌ MUITO LENTO - N+1 queries
tasks = Task.objects.all() # 1 query
for task in tasks:
print(task.created_by.username) # 1 query para cada task!
print(task.project.name) # Mais 1 query para cada!
# Total: 1 + 2N queries
# ✅ RÁPIDO - 2 queries total
tasks = Task.objects.select_related(
'created_by',
'project'
).all()
for task in tasks:
print(task.created_by.username) # Sem query adicional
print(task.project.name) # Sem query adicional
# Total: 1 query otimizadaPor que isso importa: N+1 pode tornar sua API 100x mais lenta. Sempre use select_related para ForeignKey e prefetch_related para ManyToMany.
2. Não Usar Transactions
O problema: Operações complexas sem atomicidade
# ❌ PERIGOSO - Se falhar no meio, dados ficam inconsistentes
def transfer_tasks(from_user, to_user):
tasks = Task.objects.filter(created_by=from_user)
for task in tasks:
task.created_by = to_user
task.save() # Se falhar aqui, algumas tasks foram transferidas!
# ✅ SEGURO - Tudo ou nada
from django.db import transaction
@transaction.atomic
def transfer_tasks(from_user, to_user):
tasks = Task.objects.filter(created_by=from_user)
for task in tasks:
task.created_by = to_user
task.save()
# Se qualquer operação falhar, TUDO é revertidoPrevenção: Use @transaction.atomic para operações que modificam múltiplos registros.
3. Expor Dados Sensíveis no Serializer
O problema: Retornar campos que não devem ser públicos
# ❌ PERIGOSO - Expõe senha e email
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__' # NUNCA use __all__!
# ✅ SEGURO - Apenas campos permitidos
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'first_name', 'last_name']
# Senha e email não são incluídosRed flags: Nunca use fields = '__all__' em serializers que retornam dados para o frontend.
Quando NÃO Usar Django
Não use Django quando:
Microsserviços extremamente leves: Django tem overhead. Para funções Lambda ou microsserviços minimalistas, FastAPI ou Flask são melhores
Real-time intensivo: WebSockets não são o forte do Django. Para jogos ou chat, considere Node.js com Socket.io ou Go
Machine Learning inference: Para servir modelos ML, FastAPI é mais eficiente
Protótipos de 1 hora: Se você só precisa de um endpoint rápido, Flask é mais simples
# ❌ Overkill para isso
# Django com todo seu ecosistema para servir um endpoint simples
# ✅ Flask é melhor para casos ultra-simples
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/health')
def health():
return jsonify({'status': 'ok'})Django vs Alternativas
Django vs FastAPI
Feature | Django | FastAPI |
|---|---|---|
Velocidade | Boa (sync) | Excelente (async) |
Admin Panel | Incluído | Não tem |
ORM | Django ORM | SQLAlchemy ou Tortoise |
Learning Curve | Média | Baixa |
Use Case Ideal | Apps CRUD complexas | APIs de alta performance |
Django é melhor quando: Você precisa de admin panel, auth completa, e desenvolvimento rápido
FastAPI é melhor quando: Performance é crítica e você não precisa de admin
Django vs Express.js
Feature | Django | Express.js |
|---|---|---|
Batteries-Included | Sim | Não |
Segurança | Automática | Manual |
ORM | Incluído | Precisa escolher |
Admin | Incluído | Precisa construir |
Performance I/O | Boa | Excelente |
Django é melhor quando: Desenvolvimento rápido, segurança e estrutura são prioridades
Express é melhor quando: Você precisa de máxima flexibilidade e performance de I/O
Conclusão
Django não é apenas relevante - é superior para a maioria dos casos de uso de aplicações web modernas. Sua filosofia "Batteries-Included" economiza semanas de desenvolvimento e entrega segurança robusta por padrão.
Principais vantagens:
Velocidade de desenvolvimento: Do zero à produção em dias, não semanas
Segurança automática: CSRF, XSS, SQL Injection protegidos por padrão
Admin panel gratuito: Economiza centenas de horas de desenvolvimento
ORM poderoso: Queries complexas em Python legível
Ecossistema maduro: 20 anos de melhores práticas estabelecidas
Escalabilidade comprovada: Instagram, Spotify e Pinterest usam Django
Próximos passos:
Clone o projeto exemplo e explore o código
Construa uma API REST seguindo este guia
Explore Django REST Framework e suas features avançadas
Aprenda sobre deploy com Gunicorn + Nginx + PostgreSQL
A próxima vez que você for construir uma aplicação web complexa, considere Django. Seu futuro eu (e sua equipe) agradecerão pela produtividade e robustez.
