Visión general de las respuestas HTTP#

Este es el decimotercer artículo de la serie tutorial de Django Ninja.
En este artículo ingresamos oficialmente a la sección de «Respuestas HTTP», es decir, la tercera subsección.
Esta sección presentará en 4 artículos cómo maneja Django Ninja las respuestas HTTP:
- Entrega 13: Respuestas (I) Manejo de respuestas HTTP en Django Ninja (este artículo)
- Entrega 14: Respuestas (II) Crear respuestas con estructura anidada usando Schema
- Entrega 15: Respuestas (III) ¿Por qué no usar ModelSchema? Razones por las que prefiero Django Ninja sobre DRF
- Entrega 16: Respuestas (IV) El método Resolver: Formateo de datos en campos
Explicaremos más usos de Schema; mediante estas técnicas podrás controlar con precisión el formato de salida de tu API. Ya se trate de una respuesta de objeto único o de una estructura anidada compleja, abordaremos cada caso a continuación.
Todos los cambios de código de este artículo se pueden consultar en este PR.
Proyecto de ejemplo en GitHub#
Este artículo presentará paso a paso, de lo simple a lo complejo, cómo construir respuestas HTTP mediante Django Ninja.
Y utilizaremos las 3 API existentes para demostrarlas (añadiendo diferentes contenidos según las necesidades):
- Crear publicación: Demuestra una respuesta simple añadiendo código de estado.
- Obtener publicación única: Demuestra una respuesta de objeto único, requiriendo Schema y definiendo el parámetro
response=. - Obtener lista de publicaciones: Demuestra una respuesta de múltiples objetos.
¡Comencemos!
1. Respuesta simple: Crear publicación#
Veamos primero el formato de respuesta más simple; este ejemplo mostrará cómo responder con un diccionario de Python y configurar manualmente el código de estado de la respuesta HTTP.
Tomando como ejemplo la API «Crear publicación»: (Omitiendo parte del código)
@router.post(path='/posts/')
def create_post(...) -> dict:
...
return {'id': post.id, 'title': post.title}
Lo que se responde aquí es un diccionario de Python; de hecho, puedes hacer return de cualquier dato de Python que pueda serializarse a JSON. (Por ende, los objetos modelo de Django no se pueden, ya que no se pueden serializar directamente).
Por lo tanto, se puede retornar cualquiera de los siguientes:
- Una cadena simple:
"Hello World !" - Una list de Python:
[1 , 2 , 3] - Una estructura de datos anidada:
{"name": "Alice", "age": 30, "hobbies": ["reading", "swimming"]}
Todos estos serán automáticamente serializados a formato JSON por Django Ninja y entregados como respuesta de la API:
Añadir un código de estado HTTP a la respuesta#
Al procesar respuestas, las funciones view con frecuencia deben añadir un código de estado HTTP. Especialmente cuando existen múltiples estados de respuesta, se requiere el código de estado para distinguirlos.
El procedimiento es muy sencillo: basta con añadir directamente antes del contenido de la respuesta:
De esta manera, el tipo de retorno de la función cambia del dict original a un tuple.
Por lo tanto, los type hints de la firma de nuestra función también deben corregirse en consecuencia:
Si no añades este número de código de estado al principio, Django Ninja lo establecerá por defecto en 200.
Cabe señalar que cuando tu función view vaya a retornar una respuesta «diferente a 200», debes declararlo en el decorador router:
response={201: dict} es la forma de declararlo, utilizando un diccionario de Python para hacer corresponder uno a uno el código de estado con el formato de contenido retornado.
Al momento de crear esto, el código del proyecto de ejemplo para esta parte aún no se había completado, por lo que esta API no podía responder normalmente 😅, lo menciono como aclaración.
La primera forma de respuesta mencionada arriba es muy simple, pero la mayoría de las respuestas de API no son tan sencillas.
Veamos la segunda forma de respuesta.
2. Respuesta de objeto modelo único: Obtener publicación única#
Al desarrollar una API en Django, una gran parte de los datos en la respuesta proviene de la serialización de objetos modelo de Django.
Sin embargo, por lo general no enviamos directamente toda la información de la base de datos al frontend. Al contrario, realizamos filtrado de campos, validación o transformación de formatos.
Esto no solo permite controlar con precisión la salida de la API, sino que también garantiza la corrección y seguridad de los datos.
En Django Ninja, estos requerimientos de «filtrado, validación y transformación de formatos» se realizan todos a través de Schema.
Diseñemos un formato de respuesta para la API «Obtener publicación única» usando Schema.
# post/schemas.py
from datetime import datetime
...
class PostResponse(Schema):
id: int
title: str
content: str
author_id: int
created_at: datetime
updated_at: datetime
Este Schema PostResponse contiene casi todos los campos de Post.
Ten en cuenta que la definición del Schema determinará los campos de salida. Si en el Schema solo estuviera el campo id, el resultado de salida solo tendría los datos de ese campo.
A continuación, utilizamos este Schema en la función view:
@router.get(path='/posts/{int:post_id}/', response=PostResponse)
def get_post(request: HttpRequest, post_id: int) -> Post:
"""
Obtener una sola publicación
"""
post = Post.objects.get(id=post_id)
return post
¡Solo se modificó una línea!: Añadir response=PostResponse en el decorador router.
Con la configuración response=PostResponse, Django Ninja entregará el objeto modelo Post retornado por la función a PostResponse para su validación, y tras tener éxito, lo convertirá directamente a formato JSON para enviarlo de vuelta al frontend.
Veamos el resultado de la respuesta:
// http://127.0.0.1:8000/posts/2/
{
"id": 2,
"title": "Alice's Django Ninja Post 1",
"content": "Alice's Django Ninja Post 1 content",
"author_id": 1,
"created_at": "2024-09-12T02:28:16.801Z",
"updated_at": "2024-09-12T02:28:16.801Z"
}
¡Excelente!
3. Respuesta de múltiples objetos modelo: Obtener lista de publicaciones#
Las «listas o catálogos» también son una forma común de respuesta en las API, conteniendo múltiples registros de datos.
Sigamos usando el PostResponse anterior sin hacer ningún cambio, aplicándolo directamente a la API de «obtener lista de publicaciones».
De igual manera, solo requiere cambiar una línea, pero es ligeramente diferente a lo anterior:
Utilizamos list[PostResponse], indicando que la respuesta será una list de objetos PostResponse.
Django Ninja maneja automáticamente los Iterable#
Sin embargo, en la práctica no necesitas hacer return «realmente» de una list de Python; puedes retornar directamente un QuerySet, y Django Ninja se encargará por sí solo de la iteración y serialización de los objetos.
De hecho, siempre que lo que retornes sea un iterable y cada elemento del iterable pueda pasar la validación de PostResponse (cumpliendo con el formato), ¡será suficiente!
Veamos el resultado; como la lista es demasiado larga, la presentaré mediante una captura de pantalla:

Respuesta de múltiples códigos de estado#
Las respuestas mencionadas anteriormente eran de 200 o 201, pero normalmente las API suelen incluir respuestas como 400, 401, 403 e incluso 500. ¿Cómo se maneja la relación de correspondencia entre ellas?
¡Correcto! Se logra ampliando el diccionario dentro de response=. Veamos directamente el ejemplo de la documentación oficial:
class Token(Schema):
token: str
expires: date
class Message(Schema):
message: str
@api.post(
path='/login',
response={200: Token, 401: Message, 402: Message}
)
...
Vale la pena señalar que las llaves (key) del diccionario no pueden repetirse, ¡pero los valores sí! —Message aparece dos veces.
Sin embargo, me parece que esta configuración de «respuesta de múltiples códigos de estado» no es muy práctica en el trabajo real. ¿Por qué? Lo hablaremos más adelante.
Resumen#
En este artículo comenzamos desde la respuesta más simple e introdujimos gradualmente cómo retornar datos únicos y múltiples en la respuesta, además de mencionar cómo configura Django Ninja las respuestas de múltiples códigos de estado.
En el próximo artículo exploraremos cómo manejar las estructuras anidadas complejas en las respuestas, haciendo que nuestra API sea cada vez más sólida.