Enrutamiento en Django Ninja#

Este es el octavo artículo de la serie tutorial de Django Ninja.
En el artículo anterior, presentamos la forma tradicional de configurar rutas en Django.
Como se mencionó antes, tener un «catálogo de rutas» ciertamente está bien. Sin embargo, a medida que el proyecto crece, alternar constantemente de un lado a otro entre urls.py y views.py aumenta drásticamente la carga cognitiva del desarrollador: prolonga el tiempo de desarrollo y hace que sea más fácil cometer errores.
Django Ninja adopta un diseño de enrutamiento más moderno, combinando las filosofías de diseño de Flask y FastAPI. No solo simplifica la definición de rutas, sino que también mejora la legibilidad del código, combinando estrechamente las rutas con las funciones view.
Novedades del proyecto de ejemplo#
Los cambios de código sobre la configuración de rutas en este artículo se pueden consultar en este PR (Pull Request).
Después de fusionar el PR de este artículo, el proyecto de ejemplo se ha convertido oficialmente en un «proyecto API», aunque actualmente (refiriéndonos al estado de este commit) aún no puede funcionar con normalidad, ya que todavía no hemos perfeccionado las funcionalidades básicas de las funciones view.
Puedes ir aprendiendo el nuevo contenido paso a paso siguiendo el PR de cada entrega. Esa es precisamente mi intención al crear un PR para cada artículo.
Proyecto de ejemplo en GitHub#
Ahora, comencemos a presentar la configuración de rutas en Django Ninja.
Visión general del enrutamiento en Django Ninja#
Django Ninja utiliza decoradores de Python (decorator) para definir rutas y métodos HTTP. Este enfoque combina estrechamente las rutas con las funciones view, lo que mejora enormemente la legibilidad del código.
Quienes conocen Python saben que este enfoque de «usar decoradores para definir rutas» provino originalmente de Flask. Como marco ligero, Flask fue pionero en introducir este diseño limpio y elegante, considerado una innovación ejemplar.
Posteriormente, este diseño fue adoptado por otros marcos de trabajo; por ejemplo, FastAPI y el Django Ninja de este artículo heredaron este modo flexible de definición de rutas.
En Flask, los desarrolladores pueden definir rutas así:
Django Ninja también adopta un concepto similar, solo que en sintaxis se integra mejor con el ecosistema de Django y combina pistas de tipo (type hint) con la validación de datos de Pydantic, haciendo que el desarrollo de API sea más moderno.
A continuación se muestra un ejemplo sencillo de Django Ninja:
from ninja import NinjaAPI
api = NinjaAPI()
@api.get('/')
def hello(request):
return {"message": "Hello, Django Ninja!"}
Más que decir que estas dos formas de escribir en Flask y Django Ninja son muy similares, ¡diría que son idénticas! 😎
Un enfoque más organizado: usar el objeto Router#
Aunque usar directamente NinjaAPI como en el ejemplo anterior para definir rutas es simple e intuitivo, en el trabajo real recomendamos utilizar el objeto Router (documentación oficial) para gestionar las rutas de las diferentes apps de Django.
from ninja import Router
router = Router() # Crear objeto Router
@router.get(path='/')
def hello(request):
return {"message": "Hello, Django Ninja!"}
Esto coincide con el espíritu fundamental de «distinguir entre rutas de primer y segundo nivel» de la práctica tradicional de Django. No solo mantiene clara la arquitectura del proyecto, sino que también permite que la lógica de cada app permanezca independiente.
En Django Ninja, el objeto Router proporciona una forma modular de configuración de rutas, permitiendo que cada app de Django gestione sus propias rutas y se integren de manera unificada en el api.py a nivel de proyecto, reemplazando así la función del tradicional urls.py.
En los siguientes ejemplos de código, implementaremos todo utilizando el objeto Router.
Cambios en la arquitectura del proyecto#
Primero veamos qué cambios ocurren en la estructura de un proyecto tradicional de Django tras adoptar Django Ninja.
Estructura de rutas tradicional de Django#
Tomando el proyecto de ejemplo como referencia, esta es la estructura típica de un Django tradicional:
├── NinjaForum
│ ├── urls.py # Rutas de primer nivel del proyecto
│ ├── ...
├── post
│ ├── urls.py # Rutas de segundo nivel de la app
│ ├── view.py # Lugar donde se colocan las funciones view de la app
│ ├── ...
├── user
│ ├── urls.py # Rutas de segundo nivel de la app
│ ├── view.py # Lugar donde se colocan las funciones view de la app
│ ├── ...
├── ...
El urls.py a nivel de app de Django se encarga de definir todas las rutas internas de la app, y luego el urls.py del proyecto las integra. Todo está bien ordenado y con responsabilidades claras.
Estructura de rutas en Django Ninja#
Tras adoptar Django Ninja, habrá algunos cambios en la estructura del proyecto. A continuación se muestra una estructura típica de proyecto en Django Ninja:
├── NinjaForum
│ ├── urls.py # Rutas de «nivel cero» del proyecto
│ ├── api.py # Rutas de primer nivel del proyecto
│ ├── ...
├── post
│ ├── api.py # Rutas + funciones view de la app post
│ ├── ...
├── user
│ ├── api.py # Rutas + funciones view de la app user
│ ├── ...
├── ...
En esta estructura, cada app de Django cuenta con un api.py, utilizado para definir todas las rutas de API y funciones view de esa app, reemplazando las funciones de urls.py y views.py del Django tradicional.
El api.py a nivel de proyecto se encarga de integrar las API de todas las apps de Django.
Además, el urls.py del proyecto sigue siendo necesario, ya que se encarga de reintegrar las rutas de la API de Django Ninja en la configuración URL de Django. Al mismo tiempo, puede actuar como una ruta de «nivel cero», añadiendo un prefijo de ruta unificado para todo el proyecto a todas las API, por ejemplo /api/.
Implementación de rutas en Django Ninja#
Habiendo comprendido la estructura de rutas en Django Ninja, implementemos directamente dos API en las dos apps de Django del proyecto de ejemplo: «obtener todos los usuarios» y «obtener lista de publicaciones».
Iremos perfeccionando estas API de forma gradual en los siguientes artículos. Por ahora son solo un prototipo, así que enfoquémonos primero en la configuración de las rutas.
1. Crear rutas de segundo nivel#
Crea un archivo api.py en la app user con el siguiente contenido:
# user/api.py
from ninja import Router
router = Router()
@router.get(path='/')
def get_users(request):
users = User.objects.all()
return users
De igual manera, creamos una ruta y función view similar en post/api.py:
# post/api.py
from ninja import Router
router = Router()
@router.get(path='/')
def get_posts(request):
posts = Post.objects.all()
return posts
De esta forma, hemos creado las API por separado para las apps user y post. A continuación, necesitamos integrar estas rutas en las rutas de API a nivel de proyecto.
2. Crear rutas de primer nivel#
Dentro del directorio del proyecto Django (refiriéndonos al directorio NinjaForum), también necesitamos crear un api.py. Este servirá como nuestra ruta de primer nivel, integrando las API de todas las apps. A continuación se muestra el contenido de este api.py:
# NinjaForum/api.py
from ninja import NinjaAPI
api = NinjaAPI()
api.add_router(prefix='/users/', router='user.api.router')
api.add_router(prefix='/posts/', router='post.api.router')
Vale la pena señalar que existen dos formas de escribir la integración de rutas aquí; la anterior es la que acostumbro usar.
La otra forma es importar directamente el objeto router:
from user.api import router as user_router
from post.api import router as post_router
api.add_router(prefix='/users/', router=user_router)
api.add_router(prefix='/posts/', router=post_router)
Ambos métodos son equivalentes en términos de funcionalidad, y elegir uno u otro depende principalmente de la preferencia personal y de la forma de organización del proyecto.
3. Archivo urls.py del proyecto#
En Django Ninja, el urls.py a nivel de proyecto se transforma en el puente que conecta Django con la API de Django Ninja.
En el urls.py del proyecto, también podemos definir un prefijo de ruta compartido para todo el proyecto. Se ve así:
from django.contrib import admin
from django.urls import path
from NinjaForum.api import api
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', api.urls),
]
Aquí se define un prefijo de ruta del proyecto: /api/.
De esta manera, el endpoint de la API para «obtener todas las publicaciones» será:
Por supuesto, si no necesitas un prefijo de ruta adicional, puedes omitirlo directamente:
Hasta aquí, hemos completado la configuración de rutas en Django Ninja.
Esta estructura no solo mantiene el diseño modular original de Django, sino que también ofrece una mayor flexibilidad para nuestro desarrollo de API.
Cierre de esta sección y siguientes pasos#
En la primera sección, aprendimos a definir rutas usando Django Ninja y comprendimos la diferencia entre el enrutamiento tradicional de Django y el enrutamiento en Django Ninja.
El enfoque de enrutamiento de Django Ninja no solo hace que el código sea más legible, sino que también mantiene una estructura clara en el proyecto, mejorando algunas de las desventajas del enrutamiento tradicional de Django.
En el próximo artículo, entraremos a la parte central de la API en Django Ninja: las funciones view.