Django Ninja를 사용하여 Django에서 FastAPI 스타일 API 구축하기
Takashi Yamamoto
Infrastructure Engineer · Leapcell

소개
웹 개발 세계에서 확고한 위치를 차지하고 있는 Django는 강력한 애플리케이션 구축을 위한 포괄적인 프레임워크를 제공하는 "배터리 포함" 철학으로 유명합니다. 하지만 API 개발, 특히 높은 성능, 명확한 스키마 정의, 개발자 친화적인 경험을 요구하는 시나리오에서는 일부 개발자들이 FastAPI와 같은 최신 프레임워크로 눈을 돌립니다. Pydantic 및 타입 힌트에 의해 구동되는 비동기 기능, 자동 데이터 유효성 검사, 대화형 문서로 유명한 FastAPI는 믿을 수 없을 정도로 효율적인 워크플로우를 제공합니다. 하지만 Django 생태계에 깊이 몰입되어 있고, 기존 프로젝트를 포기하거나 완전히 새로운 생태계를 배우지 않고도 이러한 최신 API 개발 패러다임을 활용하고 싶다면 어떻게 해야 할까요? 바로 이때 Django Ninja
가 등장합니다. Django 개발자가 FastAPI 스타일의 타입-주석 붙은 API를 구축할 수 있도록 연결하는 역할을 하며, 친숙한 Django 프로젝트 환경 내에서 자동 유효성 검사, 직렬화, 대화형 문서의 이점을 누릴 수 있습니다. 이 문서는 Django Ninja
가 Django 개발자가 이러한 조합을 달성하도록 어떻게 힘을 실어주는지, 두 세계의 장점을 모두 결합하는 방법을 자세히 설명합니다.
Django의 최신 API 개발 경험
Django Ninja
의 구체적인 내용으로 들어가기 전에, 이 최신 API 개발 접근 방식의 근간을 이루는 몇 가지 핵심 용어를 명확히 하겠습니다.
- 타입 힌트 (Type Hinting): Python 3.5에 도입된 타입 힌트는 개발자가 함수 인자 및 반환 값의 예상 타입을 선언할 수 있도록 합니다. 런타임에 타입을 강제하지는 않지만, Pydantic과 같은 정적 분석 도구, IDE, 프레임워크가 데이터 유효성 검사 및 직렬화에 매우 유용합니다.
- Pydantic: Python 타입 주석을 사용하는 데이터 유효성 검사 및 설정 관리 라이브러리입니다. Pydantic 모델은 들어오는 요청 본문과 나가는 응답의 스키마를 정의하는 데 사용되며, 자동으로 데이터를 유효성 검사하고 상세한 오류 메시지를 생성합니다.
- OpenAPI (이전 Swagger): RESTful API를 설명하기 위한 언어 불가지론적, 개방형 표준입니다. 사람과 컴퓨터가 소스 코드, 문서 또는 네트워크 트래픽 검사 없이 서비스의 기능을 검색하고 이해할 수 있도록 합니다.
- 대화형 API 문서 (Swagger UI/ReDoc): API의 OpenAPI 사양을 기반으로 대화형 시각적 문서를 자동으로 생성하는 도구입니다. 이를 통해 개발자는 웹 브라우저에서 직접 API 엔드포인트, 요청 테스트, 데이터 모델 이해를 할 수 있습니다.
- 비동기 프로그래밍 (
async
/await
): 메인 스레드를 차단하지 않고 작업의 동시 실행을 허용하는 프로그래밍 패러다임입니다. 이는 웹 애플리케이션에서 I/O 바운드 작업(데이터베이스 쿼리 또는 외부 API 호출 등)에 특히 유익하며, 성능 및 확장성 향상으로 이어집니다.
Django Ninja
는 이러한 개념을 활용하여 Django 내에서 FastAPI와 유사한 경험을 제공합니다. Django 뷰를 감싸는 역할을 하며, 타입 힌트 및 Pydantic 모델을 해석하여 자동 데이터 유효성 검사, 직렬화, OpenAPI 스키마 생성을 수행합니다.
Django Ninja 시작하기
실제 예제를 통해 살펴보겠습니다. Django 프로젝트에서 "Items"를 관리하는 간단한 API를 구축해 보겠습니다.
먼저 Django가 설치되어 있고 프로젝트가 설정되어 있는지 확인하세요. 그런 다음 django-ninja
를 설치합니다.
pip install django-ninja pydantic
settings.py
에서 Django 프로젝트의 INSTALLED_APPS
에 'ninja'
를 추가합니다.
# myproject/settings.py INSTALLED_APPS = [ # ... 'django.contrib.staticfiles', 'ninja', # django-ninja 추가 ]
이제 Item
에 대한 간단한 Django 모델을 정의해 보겠습니다.
# myapp/models.py from django.db import models class Item(models.Model): name = models.CharField(max_length=255) description = models.TextField(blank=True) price = models.DecimalField(max_digits=10, decimal_places=2) is_available = models.BooleanField(default=True) def __str__(self): return self.name
마이그레이션을 생성하고 적용합니다.
python manage.py makemigrations myapp python manage.py migrate
Django Ninja로 API 엔드포인트 정의하기
Django Ninja
의 핵심은 API 라우터를 정의하는 데 있습니다. API 로직을 보관할 myapp/api.py
파일을 만듭니다.
# myapp/api.py from ninja import NinjaAPI, Schema from typing import List from django.shortcuts import get_object_or_404 from .models import Item api = NinjaAPI() # 1. 요청 본문 및 응답을 위한 Pydantic 스키마 정의 class ItemIn(Schema): name: str = ... # 필수 필드 description: str = None # 선택 필드 price: float is_available: bool = True class ItemOut(Schema): id: int name: str description: str price: float is_available: bool # 선택 사항: Pydantic이 ORM 객체와 작동하도록 구성 class Config: orm_mode = True # 2. 타입 힌트를 사용하여 API 엔드포인트 정의 @api.post("/items", response=ItemOut) def create_item(request, item_in: ItemIn): """ 새 항목을 생성합니다. """ item = Item.objects.create(**item_in.dict()) return item @api.get("/items", response=List[ItemOut]) def list_items(request): """ 모든 항목 목록을 검색합니다. """ return Item.objects.all() @api.get("/items/{item_id}", response=ItemOut) def get_item(request, item_id: int): """ ID로 단일 항목을 검색합니다. """ item = get_object_or_404(Item, id=item_id) return item @api.put("/items/{item_id}", response=ItemOut) def update_item(request, item_id: int, item_in: ItemIn): """ 기존 항목을 업데이트합니다. """ item = get_object_or_404(Item, id=item_id) for attr, value in item_in.dict(exclude_unset=True).items(): setattr(item, attr, value) item.save() return item @api.delete("/items/{item_id}", status_code=204) def delete_item(request, item_id: int): """ ID로 항목을 삭제합니다. """ item = get_object_or_404(Item, id=item_id) item.delete() return
Django URL과 통합하기
마지막으로 NinjaAPI
인스턴스를 Django 프로젝트의 urls.py
에 연결합니다.
# myproject/urls.py from django.contrib import admin from django.urls import path from myapp.api import api # Ninja API 인스턴스 가져오기 urlpatterns = [ path('admin/', admin.site.urls), path("api/", api.urls), # Ninja API 마운트 ]
이제 개발 서버를 실행합니다.
python manage.py runserver
http://127.0.0.1:8000/api/docs
(Swagger UI) 또는 http://127.0.0.1:8000/api/redoc
(ReDoc)에서 대화형 API 문서를 액세스할 수 있습니다. 이 문서는 ItemIn
및 ItemOut
스키마와 API 엔드포인트 함수의 타입 힌트를 기반으로 자동으로 생성됩니다.
입증된 주요 이점
- 자동 데이터 유효성 검사:
/api/items
에POST
요청을 하면Django Ninja
가ItemIn
스키마에 대해 요청 본문을 자동으로 유효성 검사합니다. 필수 필드가 누락되거나 타입이 잘못된 경우, 자세한 메시지와 함께 명확한 422 Unprocessable Entity 오류가 반환됩니다. - 자동 직렬화:
response=ItemOut
이 있는 엔드포인트에서 DjangoItem
객체를 반환하면,Django Ninja
가ItemOut
스키마와 호환되는 사전으로 자동으로 변환하여 직렬화를 처리합니다. - 대화형 문서: 문서 한 줄도 작성하지 않고도 완전한 대화형 Swagger UI 및 ReDoc 페이지를 얻을 수 있습니다. 이를 통해 브라우저에서 직접 엔드포인트, 데이터 모델을 탐색하고 테스트 요청을 보낼 수도 있습니다.
- 타입 힌트 기반 개발:
item_in: ItemIn
및item_id: int
를 사용하면 API의 입력 및 출력 타입이 명시적이 되어 코드 가독성, 유지보수성을 향상시키고 더 나은 정적 분석을 가능하게 합니다. - 빠른 개발: 유효성 검사 및 직렬화를 자동화함으로써 개발자는 상용구 코드보다는 비즈니스 로직에 더 집중할 수 있습니다.
비동기 작업 (선택 사항)
Django Ninja
는 비동기 뷰 함수도 지원하며, 최신 버전에서 도입된 Django의 비동기 기능과 자연스럽게 어울립니다. 예를 들어, list_items
엔드포인트를 비동기화하려면(비동기 지원 ORM이 있거나 비동기 I/O를 수행하는 경우):
# myapp/api.py (스니펫) # ... import asyncio @api.get("/items", response=List[ItemOut]) async def list_items_async(request): """ 비동기적으로 모든 항목 목록을 검색합니다. """ # 비동기 작업 시뮬레이션 (예: 나중에 비동기 데이터베이스 호출) await asyncio.sleep(0.01) # 참고: Django ORM은 기본적으로 동기식입니다. 실제 비동기 ORM의 경우, # django-async-orm과 같은 라이브러리를 사용하거나 sync_to_async로 변환해야 합니다. return list(Item.objects.all()) # 여전히 래핑하거나 async_to_sync 해야 합니다.
Django ORM과의 실제 비동기 데이터베이스 상호 작용을 위해서는 일반적으로 asgiref.sync
의 sync_to_async
를 사용하거나 비동기 ORM 래퍼를 사용합니다. 그러나 @api.get()
데코레이터는 async def
함수를 직접 처리할 수 있습니다.
고급 사용 및 적용 시나리오
Django Ninja
는 확장성이 뛰어납니다. 다음을 수행할 수 있습니다.
- 인증 구현: Django의 인증 시스템과 통합하거나
Django Ninja
의 내장APIKey
또는JWT
인증을 사용합니다. - 의존성 추가: FastAPI의 의존성 주입과 유사하게 일반적인 의존성(데이터베이스 세션, 현재 사용자 또는 외부 클라이언트 등)을 API 함수에 주입합니다.
- 오류 처리: 사용자 정의 예외 처리기로 오류 응답을 사용자 정의합니다.
- 라우터 구성: 더 나은 구성을 위해 대규모 API를 더 작고 모듈식 라우터로 분해합니다.
- 쿼리 매개변수 및 경로 매개변수: 타입 힌트를 사용하여 쿼리 및 경로 매개변수를 쉽게 정의하고 유효성을 검사합니다.
Django Ninja
는 다음과 같은 프로젝트에 이상적입니다.
- 기존 Django 애플리케이션 위에 고성능 RESTful API를 구축해야 하는 경우.
- 강력한 타입 계약, 자동 데이터 유효성 검사, 명확한 API 문서를 가치 있게 여기는 경우.
- Django 생태계 내에서 FastAPI 개발 경험의 모범 사례를 활용하고 싶은 경우.
- 깔끔하고 잘 문서화된 API가 중요한 단일 페이지 애플리케이션(SPA) 또는 모바일 백엔드를 구축하는 경우.
결론
Django Ninja
는 Django의 강력하고 검증된 기능과 FastAPI가 개척한 최신 개발자 친화적 패러다임을 성공적으로 결합합니다. 타입 힌트와 Pydantic을 채택함으로써 Django 프로젝트 내에서 성능이 뛰어나고 자체 문서화되는 API를 구축할 수 있는 우아하고 효율적인 방법을 제공하며, 상용구 코드를 크게 줄이고 개발 속도와 코드 품질을 향상시킵니다. FastAPI와 유사한 경험으로 API 게임을 향상시키고 싶은 Django 개발자라면 Django Ninja
는 필수적인 도구입니다. 이를 통해 아름다운 API를 빠르고 자신 있게 구축할 수 있습니다.