콘텐츠로 이동

Django Ninja 라우팅#

2024 iThome 철인 대회

이 글은 Django Ninja 입문 가이드의 8번째 글이에요.

이전 글에서는 기존 Django의 라우팅 설정 방식에 대해 소개했어요.

앞서 언급했듯이, '라우팅 목록'을 갖는 것은 분명 좋은 점이 있어요. 하지만 프로젝트 규모가 커지면서 urls.pyviews.py끊임없이 왔다 갔다 하는 것은 개발자의 인지적 부담을 크게 가중시켜 개발 시간을 길어지게 하고 더 많은 오류를 초래하기 쉬워요.

Django Ninja는 Flask와 FastAPI의 설계 철학을 결합하여 더욱 현대적인 라우팅 설계를 채택했어요. 이는 라우팅 정의를 단순화할 뿐만 아니라 코드의 가독성을 높이고 라우팅과 뷰(view) 함수를 밀접하게 결합시켰어요.

예제 프로젝트 동향#

이번 글과 관련된 라우팅 설정의 코드 변경 사항은 이 PR(Pull Request)을 참고해 주세요.

예제 프로젝트에 이번 글의 PR이 병합되면 정식으로 'API 프로젝트'가 되지만, 현재 상태(이 commit 기준)로는 아직 정상적으로 작동하지 않아요. 왜냐하면 아직 뷰 함수의 기본 기능을 완벽하게 구현하지 않았기 때문이에요.

여러분은 각 글의 PR을 따라가며 단계별로 새로운 내용을 학습할 수 있어요. 이것이 제가 각 글마다 PR을 만든 이유이기도 해요.

GitHub 예제 프로젝트#

👉 Django-Ninja-Tutorial


이제 Django Ninja의 라우팅 설정에 대해 소개해 볼게요.

Django Ninja 라우팅 개요#

Django Ninja는 Python 데코레이터(decorator)를 사용하여 라우팅과 HTTP 메서드를 정의해요. 이 방식은 라우팅과 뷰 함수를 밀접하게 연결하여 코드의 가독성을 크게 향상시켜요.

Python에 익숙한 분이라면 아시겠지만, 사실 이런 '데코레이터를 사용한 라우팅 정의' 방식은 Flask에서 처음 시작되었어요. 가벼운 프레임워크인 Flask가 도입한 이런 간결하고 우아한 설계모범적인 혁신이라고 할 수 있어요.

이 설계는 이후 다른 프레임워크에도 채택되었는데, 대표적으로 FastAPI와 본 글의 Django Ninja 모두 이런 유연한 라우팅 정의 모델을 계승했어요.

Flask에서는 다음과 같이 라우팅을 정의할 수 있어요:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, Flask!'

Django Ninja도 비슷한 개념을 채택했지만, 문법적으로 Django 생태계에 더 잘 통합되어 있어요. 또한 타입 힌트(type hint) 및 Pydantic의 데이터 검증 기능과 결합하여 API 개발을 훨씬 더 현대적으로 만들었어요.

다음은 Django Ninja의 간단한 예제예요:

from ninja import NinjaAPI

api = NinjaAPI()

@api.get('/')
def hello(request):
    return {"message": "Hello, Django Ninja!"}

Flask와 Django Ninja의 두 방식은 매우 비슷하다기보다는, 완전히 똑같다고 할 수 있어요😎


더 체계적인 방식: Router 객체 사용#

위 예제처럼 NinjaAPI를 직접 사용하여 라우팅을 정의하는 것은 간단하고 직관적이지만, 실제 작업 환경에서는 Router 객체(공식 문서)를 사용하여 여러 Django 앱의 라우팅을 관리하는 것을 더 권장해요.

from ninja import Router

router = Router()  # Router 객체 생성

@router.get(path='/')
def hello(request):
    return {"message": "Hello, Django Ninja!"}

이것은 기존 Django에서 '1차 라우팅과 2차 라우팅을 분리'하던 기본 정신과 일맥상통해요. 프로젝트 아키텍처를 명확하게 유지할 뿐만 아니라 각 앱의 로직을 독립적으로 유지할 수 있게 해줘요.

Django Ninja에서 Router 객체는 모듈화된 라우팅 설정 방식을 제공하여, 각 Django 앱이 자체적으로 라우팅을 관리하고 이를 프로젝트 레벨의 api.py에서 통합할 수 있도록 해요. 즉, 전통적인 urls.py 기능을 대체하는 셈이죠.

아래의 코드 예제들은 모두 Router 객체를 사용하여 구현할 거예요.


프로젝트 아키텍처 변화#

Django Ninja를 도입한 후, 기존 Django 프로젝트의 구조가 어떻게 변하는지 먼저 살펴볼게요.

기존 Django 라우팅 구조#

예제 프로젝트를 바탕으로 한, 기존 Django의 전형적인 구조는 다음과 같아요:

├── NinjaForum
   ├── urls.py  # 프로젝트 1차 라우팅
   ├── ...
├── post
   ├── urls.py  # 앱 2차 라우팅
   ├── view.py  # 앱 소속 뷰 함수가 위치한 곳
   ├── ...
├── user
   ├── urls.py  # 앱 2차 라우팅
   ├── view.py  # 앱 소속 뷰 함수가 위치한 곳
   ├── ...
├── ...

Django 앱 레벨의 urls.py가 앱 내의 모든 라우팅 정의를 담당하고, 이후 프로젝트의 urls.py에서 이를 통합해요. 정연하고 권한 분배가 명확하죠.

Django Ninja 라우팅 구조#

Django Ninja를 도입하면 프로젝트 구조에 약간의 변화가 생겨요. 다음은 전형적인 Django Ninja 프로젝트 구조예요:

├── NinjaForum
   ├── urls.py  # 프로젝트 '0차' 라우팅
   ├── api.py   # 프로젝트 1차 라우팅
   ├── ...
├── post
   ├── api.py   # post 앱의 라우팅 + 뷰 함수
   ├── ...
├── user
   ├── api.py   # user 앱의 라우팅 + 뷰 함수
   ├── ...
├── ...

이 구조에서는 각 Django 앱이 api.py를 가지게 돼요. 이것은 해당 앱의 모든 API 라우팅과 뷰 함수를 정의하는 데 쓰이며, 기존 Django의 urls.pyviews.py 역할을 동시에 대체해요.

프로젝트 레벨의 api.py는 모든 Django 앱의 API를 통합하는 역할을 담당해요.

또한, 프로젝트의 urls.py여전히 필요해요. 이 파일은 Django Ninja의 API 라우팅을 Django의 URL 설정에 다시 연결하는 역할을 해요. 동시에 '0차' 라우팅이 되어 모든 API에 /api/와 같이 프로젝트 전체에 통일된 라우팅 접두사를 추가할 수도 있어요.


Django Ninja 라우팅 실습#

Django Ninja의 라우팅 구조를 이해했으니, 이제 예제 프로젝트의 두 Django 앱에 각각 '모든 사용자 가져오기'와 '게시글 목록 가져오기' 두 개의 API를 직접 구현해 볼게요.

우리는 앞으로 이어질 여러 글을 통해 점진적으로 이 API들을 완성해 나갈 거예요. 지금은 단지 프로토타입일 뿐이므로, 일단 라우팅 설정에 초점을 맞춰 주세요.

1. 2차 라우팅 생성#

user 앱 안에 api.py를 생성하고 내용은 다음과 같이 작성해요:

# user/api.py
from ninja import Router

router = Router()

@router.get(path='/')
def get_users(request):
    users = User.objects.all()
    return users

마찬가지로 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

이렇게 우리는 user와 post 두 앱에 각각 API를 생성했어요. 이제 이 라우팅들을 프로젝트 레벨의 API 라우팅에 통합해야 해요.

2. 1차 라우팅 생성#

Django 프로젝트 디렉토리(NinjaForum 디렉토리) 하위에도 api.py를 생성해야 해요. 이것이 모든 앱의 API를 통합하는 1차 라우팅 역할을 할 거예요. 이 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')

참고로, 라우팅 통합에는 두 가지 작성 방식이 있어요. 위의 것이 제가 즐겨 쓰는 방식이에요.

다른 한 가지 방식은 router 객체를 직접 import 하는 거예요:

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)

이 두 가지 방법은 기능적으로 동일해요. 어느 쪽을 선택할지는 주로 개인적인 선호도와 프로젝트 구성 방식에 달려 있어요.

3. 프로젝트 urls.py#

Django Ninja에서 프로젝트 레벨의 urls.py는 Django와 Django Ninja API를 이어주는 다리 역할을 해요.

프로젝트 urls.py에서는 프로젝트 전체가 공유하는 라우팅 접두사를 추가로 정의할 수 있어요. 다음과 같이요:

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),
]

여기서 /api/라는 프로젝트 라우팅 접두사가 정의되었어요.

이렇게 되면 '모든 게시글 가져오기'의 API 엔드포인트는 다음과 같이 될 거예요:

/api/posts/

물론 추가적인 라우팅 접두사가 필요 없다면 과감히 생략할 수도 있어요:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', api.urls),  # 접두사 생략
]

이것으로 Django Ninja의 라우팅 설정을 완료했어요.

이러한 구조는 Django 원래의 모듈화된 설계를 유지하면서도 우리의 API 개발에 훨씬 더 큰 유연성을 제공해 줘요.


본 절 마무리 및 다음 단계#

1절에서는 Django Ninja를 사용하여 라우팅을 정의하는 방법을 배우고 기존 Django 라우팅과 Django Ninja 라우팅의 차이점을 알아보았어요.

Django Ninja의 라우팅 접근 방식은 코드의 가독성을 높일 뿐만 아니라 프로젝트의 명확한 구조를 유지하여, 기존 Django 라우팅의 일부 단점을 개선했어요.

다음 글에서는 Django Ninja API의 핵심 부분인 뷰(view) 함수로 들어갈 거예요.