REST frente a GraphQL: cuando aporta
DRF expone recursos (/api/productos/, /api/pedidos/{id}/). El cliente recibe siempre la representacion completa del recurso, y si necesita combinar productos con sus pedidos hace dos llamadas. Para una app movil con red lenta, esos round-trips suman.
GraphQL plantea un unico endpoint (/graphql/) al que el cliente envia una query declarando que campos quiere y de que tipos relacionados.
query {
producto(id: "42") {
id
nombre
precio
categoria { nombre }
resenas(first: 3) {
edges { node { puntuacion comentario } }
}
}
}
Una sola peticion HTTP, exactamente los campos solicitados. Para clientes moviles es una victoria de latencia y bateria.
GraphQL gana en: clientes moviles, agregadores que combinan multiples recursos, frontends que evolucionan rapido sin tocar backend. REST gana en: caching HTTP estandar (CDN), endpoints publicos con SLAs por recurso, integraciones simples y herramientas universales (cURL, Postman, OpenAPI).
Por que strawberry-django y no graphene
graphene-django lleva anos siendo el estandar de facto pero su mantenimiento ha decaido y su API basada en clases con mucho boilerplate ya no encaja con el Django moderno. strawberry-django (2024+) usa dataclasses + type hints como FastAPI o Pydantic, soporta async nativo y tiene una integracion mucho mas natural con los modelos.
pip install strawberry-graphql-django
# settings.py
INSTALLED_APPS = [
"django.contrib.auth",
"rest_framework",
"strawberry_django",
"tienda",
]
Definir tipos a partir de modelos Django
# tienda/types.py
import strawberry
import strawberry_django
from strawberry_django import auto
from typing import List
from tienda.models import Categoria, Producto
@strawberry_django.type(Categoria)
class CategoriaType:
id: auto
nombre: auto
slug: auto
@strawberry_django.type(Producto)
class ProductoType:
id: auto
nombre: auto
precio: auto
stock: auto
categoria: CategoriaType
auto infiere el tipo desde el campo del modelo (CharField -> str, DecimalField -> Decimal, ForeignKey -> tipo relacionado). El esquema se genera automaticamente.
Queries
# tienda/schema.py
import strawberry
import strawberry_django
from typing import List
from tienda.models import Producto
from tienda.types import ProductoType
@strawberry.type
class Query:
productos: List[ProductoType] = strawberry_django.field()
producto: ProductoType = strawberry_django.field()
schema = strawberry.Schema(query=Query)
# core/urls.py
from django.urls import path
from strawberry.django.views import GraphQLView
from tienda.schema import schema
urlpatterns = [
path("graphql/", GraphQLView.as_view(schema=schema)),
]
Lanzas runserver y entras a http://localhost:8000/graphql/. strawberry incluye GraphiQL integrado para probar queries.
Filtros y ordering
import strawberry_django
from strawberry_django import filters, ordering
@strawberry_django.filter(Producto, lookups=True)
class ProductoFilter:
nombre: auto
precio: auto
categoria: auto
@strawberry_django.ordering.order(Producto)
class ProductoOrder:
precio: auto
nombre: auto
@strawberry_django.type(Producto, filters=ProductoFilter, order=ProductoOrder)
class ProductoType:
id: auto
nombre: auto
precio: auto
categoria: CategoriaType
Permite consultas como:
query {
productos(
filters: { precio: { gt: 100 }, categoria: { nombre: { iEndsWith: "phone" } } }
order: { precio: ASC }
) {
id
nombre
}
}
Paginacion Relay (first/after)
@strawberry.type
class Query:
productos: strawberry_django.relay.ListConnectionWithTotalCount[ProductoType] = (
strawberry_django.connection()
)
query {
productos(first: 5, after: "cursor-base64") {
totalCount
edges {
cursor
node { id nombre }
}
pageInfo {
hasNextPage
endCursor
}
}
}
Relay pagination es el estandar GraphQL. Cursor en base64 evita el problema de paginas inconsistentes cuando se inserta entre paginas.
Mutaciones
import strawberry_django
from strawberry_django.mutations import mutations
@strawberry.type
class Mutation:
crear_producto: ProductoType = mutations.create(ProductoInput)
actualizar_producto: ProductoType = mutations.update(ProductoInputPartial)
borrar_producto: bool = mutations.delete()
@strawberry_django.input(Producto)
class ProductoInput:
nombre: auto
precio: auto
categoria: auto
mutation {
crearProducto(data: { nombre: "Mando PS6", precio: "59.90", categoria: 3 }) {
id
nombre
}
}
Resolver el N+1 con DataLoader
Si la query pide producto.categoria para 100 productos, sin precaucion strawberry hace 100 queries SQL. La solucion es DataLoader: agrupa todas las llamadas dentro del mismo tick del event loop y emite una unica query SQL con IN (...).
from strawberry.dataloader import DataLoader
async def cargar_categorias(ids):
qs = Categoria.objects.filter(id__in=ids).in_bulk()
return [qs.get(i) for i in ids]
categoria_loader = DataLoader(load_fn=cargar_categorias)
@strawberry_django.type(Producto)
class ProductoType:
@strawberry.field
async def categoria(self, root) -> CategoriaType:
return await categoria_loader.load(root.categoria_id)
strawberry-django incluye desde su version 0.20+ un optimizer automatico que aplica
select_relatedyprefetch_relateden funcion de los campos pedidos en la query. Activalo conOptimizerExtension.
Autenticacion JWT
from strawberry.django.views import GraphQLView
from rest_framework_simplejwt.authentication import JWTAuthentication
from django.contrib.auth.models import AnonymousUser
class AuthGraphQLView(GraphQLView):
def get_context(self, request, response):
auth = JWTAuthentication()
try:
user, _ = auth.authenticate(request) or (AnonymousUser(), None)
except Exception:
user = AnonymousUser()
return {"request": request, "user": user}
@strawberry.type
class Query:
@strawberry.field
async def yo(self, info) -> str | None:
user = info.context["user"]
return user.username if user.is_authenticated else None
REST y GraphQL conviviendo
Nada impide tener /api/... con DRF y /graphql/ con strawberry en el mismo proyecto. Patron habitual:
- REST para operaciones publicas con caching CDN, webhooks salientes, integraciones B2B.
- GraphQL para el frontend interno (web, movil) que necesita combinar muchos recursos.
Ambos comparten modelos, autenticacion (JWT con simplejwt) y logica de negocio en services. Lo unico que cambia es la capa de exposicion.
flowchart LR
subgraph Cliente
WEB[Web React]
MOB[App movil]
B2B[Cliente B2B]
end
subgraph Django
REST[/api/ DRF]
GQL[/graphql/ strawberry]
SVC[Servicios + ORM]
end
WEB --> GQL
MOB --> GQL
B2B --> REST
REST --> SVC
GQL --> SVC
Limitaciones a tener en cuenta
- Caching HTTP es mas dificil con GraphQL (todo va por POST a
/graphql/). Cliente: usa Apollo Client o urql con su propio cache. - Rate limiting por endpoint no funciona; necesitas analizar la complejidad de la query (depth limit, query cost). strawberry trae
MaxAliasesLimiteryQueryDepthLimiter. - Permisos se aplican a nivel de campo, no de URL. Usa
strawberry_django.permissiono decoradores propios.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Django
Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comparar GraphQL frente a REST y entender cuando aporta. Instalar strawberry-django y exponer un endpoint /graphql/. Definir tipos a partir de modelos Django con strawberry_django.type. Crear queries y mutations tipadas. Pagina con Relay (first/after). Optimizar N+1 con DataLoader. Autenticar el endpoint con JWT. Integrar GraphQL y DRF en el mismo proyecto.
Cursos que incluyen esta lección
Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje