본문 바로가기
BE/DRF

1. DRF 시작하기

by aeyong-dev 2023. 7. 7.

DRF는 Django REST Framework의 줄임말이다. 

이제부터 DRF에 대해서 공부해 볼 것이다. 

그럼 DRF가 무엇인지 부터 알아야 하는 것이 당연하다. 

본인이 지금 무엇을 공부하고 있는지도 모른다면 큰 문제이니까..

미안합ㄴ디ㅏ 지금 살짝 정신을 놓은 상태라 아무말이나 막 하는 것 같네요 하하

 

DRF는 Django REST Framework, 말 그대로 장고를 기반으로 REST API 서버를 만들기 위한 라이브러리 이다.

데이터를 JSON 형태로 바꾸어서 장고 외의 여러 프로젝트에 데이터를 보낼 수 있게 되는 것이다.

DRF 설치는 간단하므로 생략하겠다. 


1.1 DRF 구조 살펴보기: hello API 만들어보기

 

이번 예제 api를 만들 때는 모델이 필요없이 바로 뷰를 작성한다.

기본적으로 django.shortcuts의 render가 있는데, 이를 지우고 DRF의 내용으로 새로 채워보겠다. 

from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view

@api_view(['GET'])
def HelloAPI(request):
	return Response("Hello world!")

자바의 어노테이션과 비슷한 것이 눈에 띈다. 

이것은 데코레이터 라고 한다. 

여기서는 HelloAPI 라는 함수가 GET 요청을 받는 api 라는 것을 표시해준다. 

 

request 객체는 두 가지 역할을 한다. 

  • 요청을 효과적으로 처리
  • 인증 구현 시 편리함 제공

request 객체의 정보에 접근하기 위해 다음과 같이 사용한다.

  • request.method: 요청이 어떤 타입인지(GET, POST...)
  • request.data: 무슨 데이터를 보냈는지

 

Response는 응답에 대한 정보를 갖고 있다. 

  • response.data: 응답에 포함되는 데이터
  • response.status: 응답에 대한 상태(http response status code)

 

URL

이제는 URL을 설정할 차례이다. 프로젝트 디렉토리의 url.py를 아래와 같이 수정한다. 

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('example/', include('example.urls')),
]

애플리케이션 디렉토리에 url.py를 새로 만들어 아래 코드를 작성한다.

from django.urls import path, include
from .views import HelloAPI

urlpatterns = [
    path("hello/", HelloAPI)
]

이제 서버를 실행시킨다.

404: page not found

admin/ 이나 example/ 을 통해 접속할 수 있다고 한다. 

404: page not found

exaple/hello/ 를 통해서 접속하라고 한다. 다시 해보자. 

hello API

이제서야 잘 작동되는 모습을 볼 수 있다. 

이것이 API를 만든 것이다. 

이 데이터들을 http의 헤더 라고 한다. http가 요청/응답을 할 때의 프로토콜이 http의 헤더라고 할 수 있다.

요청/응답에 대한 결과 상태(http status code), 데이터 타입 등의 정보를 포함한다.

 

우리가 만든 것 처럼, DRF는 Response를 제공하는 api다. 

그런데 우리는 JSON의 형태로 다른 웹 앱에 데이터를 제공하고 싶다. 

Response를 JSON으로 변경해주는 것DRF Serializer이다. 

일단 지금은 장고와 DRF의 차이에 대해 한번 짚고 설명하겠다. 

특징 Django DRF
개발 목적 풀스택 개발 백엔드 API 서버 개발
개발 결과 웹 페이지를 포함한 웹 서비스 여러 클라이언트에서 가용한 API 서버
응답 형태 HTML JSON
다른 파일 templates serializers.py

1.2 도서 정보 API 예제를 통한 DRF 기초 개념

DRF Serializer

Serializer의 사전적 의미는 '직렬화' 이다. 

아까 설명했듯, 시리얼라이저는 Response를 JSON으로 변환해준다. 

장고에서 내가 만든 모델의 인스턴스(내가 만든 모델에서 뽑은 queryset)를 JSON으로 바꾼다는 뜻이다.

간단하게 말하자면 장고 모델을 JSON으로 변환하기 위한 틀이다. 

 

DRF에서 데이터는 파이썬 객체의 형태로 저장된다.

DRF에서 데이터를 저장할 때는 장고의 Model을 통해서 저장한다.

앞서 말했듯이 Model은 추상화된 데이터베이스 테이블이다.

Model에 저장된 데이터는 ORM에 의해 파인썬 문법으로 처리될 수 있다.

따라서 Model 내부의 데이터는 파이썬 객체로써 저장된다.

 

우리는 API를 만들어서 이 데이터를 클라이언트에게 전송해준다.

그런데 파이썬 객체의 형태로 전송한다면 클라이언트는 읽을 수 없을 것이다. 

그래서 우리는 JSON등의 형식으로 클라이언트에게 전송해주는데, JSON으로 바꿔주는 것이 시리얼라이저 이다. 

 

반대로 클라이언트가 데이터를 서버로 보내주는 경우가 있다. 

클라이언트가 POST요청을 하는 API를 사용해 데이터를 서버로 보내면 JSON의 형식으로 보낼 것이다. 

이때 DRF에서 데이터를 저장하기 위해 JSON을 파이썬 객체로 변환시키는 것디시리얼라이즈(역직렬화) 이다. 

시리얼라이즈(직렬화)를 반대로 한다고 생각하면 된다. 

 

앞으로 만들 시리얼라이저는 직렬화와 역직렬화 모든 기능을 갖고 있다. 

요약하자면, 클라이언트와 서버 간 데이터 양식을 맞춰주는 변환기이다. 

이제 도서 정보 API를 만들면서 학습해보겠다.

 

from django.db import models

class Book(models.Model):
    bid = models.IntegerField(primary_key=True) #book id
    title = models.CharField(max_length=50) #book title
    author = models.CharField(max_length=50) #book author
    category = models.CharField(max_length=50) #book category
    pages = models.IntegerField() #book pages
    price = models.IntegerField() #book price
    published_date = models.DateField() #book published date
    description = models.TextField() #book description

먼저 다음과 같이 models.py를 작성하고 migration한다.

그리고 example 폴더 내에 serializers.py를 생성한다.

 

시리얼라이저는 파이썬 모델을 JSON으로 바꿔주는 것이기 때문에, 모델 데이터의 어떤 것을 데이터로 저장할지 정해줘야 한다.

Model의 모든 field를 JSON으로 저장할 필요가 없고, 그렇지 않는 경우가 많다고 한다. 

그래서 어떤 것을 데이터로써 저장할지 정해주기 위해 시리얼라이저에서도 필드를 선언해야 한다. 

 

그런데 이 과정은 몹시 복잡하고 번거롭다. 

그래서 serializers.ModelSerializer 라는 것을 사용해 아래와 같이 간단하게 작성할 수 있다. 

새로 생성한 serializers.py에 아래의 코드를 작성하자. 

from rest_framework import serializers
from models.py import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['bid', 'title', 'author', 'category', 'pages', 'price', 'published_date', 'description']

원래같았으면 상당히 길었어야 할 코드인데 이처럼 간단해졌다. 

ModelSerializer는 이름처럼 모델의 정보를 기반으로 동작하는 시리얼라이저 이다. 

1.2.2 DRF FBV, CBV, API View

DRF 뿐 아니라 장고에서 또한 두 가지 방법으로 뷰를 작성할 수 있다. 

바로 함수 기반 뷰(Function Based View), 클래스 기반 뷰(Class Based View) 이다.

리액트의 함수형 컴포넌트와 클래스형 컴포넌트 처럼 기능적인 차이는 없다. 

참고로 이때까지 우리가 작성한 뷰는 함수 기반 뷰 이다. 

 

이 둘 모두를 가능하게 해주는 것이 API View이다. 

API View는 여러 유형의 요청을 대응할 수 있게 해준다. 

함수형 뷰에서는 @api_view의 데코레이터 형식으로 사용한다. 

from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view

@api_view['GET']
def HelloAPI(request):
	return Response("Hello world!!")

위의 코드에서 @api_view['GET'] 이 바로 API View이다. 

클래스형 뷰에서는 APIView를 상속받는 형태로 사용한다. 


이제 실제로 도서 정보 API를 만들어 볼 것이다. 

교재에는 클래스형 View로도 설명되어 있지만, 여기서는 함수형 View만 다루도록 하겠다. 

우리가 만들 API의 구조는 다음과 같다. 

  • API(GET/book/): 전체 도서의 정보 return
  • API(GET/book/1/): bid에 해당하는 도서의 정보 return
  • API(POST/book/): 도서 정보 등록

그런데 여기서 '전체 정보를 가져오는' API주소와 '도서 정보를 등록하는' API주소는 모두 /book/ 이다. 

이것을 유의해서 다음 코드를 작성해보자.

from rest_framework import viewsets, permissions, generics, status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view
from rest_framework.generics import get_object_or_404
from .models import Book
from serializers import BookSerializer

@api_view(['GET'])
def HelloAPI(request):
    return Response("Hello World!")

@api_view(['GET', 'POST'])
def booksAPI(request):
    if request.method == 'GET':
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    elif request.method == 'POST':
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
@api_view(['GET'])
def bookAPI(request, bid):
    book=get_object_or_404(Book, pk=bid)
    serializer = BookSerializer(book)
    return Response(serializer.data, status=status.HTTP_200_OK)

먼저 BooksAPI를 살펴보자. 

요청이 GET인지 POST인지에 따라 다른 기능을 수행한다. 

GET일 경우에는, Book객체를 모두 가져와 시리얼라이저에 직렬화하여 return해준다.

데이터가 여러개이므로 시리얼라이저에 넣을 때 many=True옵션을 추가해준다. 

POST일 경우에는 역직렬화를 하여 데이터를 저장한다. 

두 경우 모두 http status response도 return해준다. 

 

BookAPI는 간단하다. 

bid에 맞는 데이터를 가져와서 직렬화하여 보여준다. 

 

1.2.3 도서 정보 API 마무리

마지막으로 우리가 만든 API를 urls.py에 연결해 볼 것이다. 

from django.urls import path, include
from .views import HelloAPI, booksAPI, bookAPI

urlpatterns = [
    path("hello/", HelloAPI),
    path("books/", booksAPI),
    path("book/<int:bid>/", bookAPI)
]

모든 준비가 끝났으니 실행시켜보자.

.../example/books/로 접속하면 다음과 같은 화면이 나온다. 

127.0.0.1:8000/example/books

도서에 대한 데이터가 하나도 없기 때문이다. 공란에 POST요청을 보낼 수 있다. 다음과 같이 JSON형태로 데이터를 보내보자. 

{
	"bid": 1,
    "title": "Book title",
    "author": "KimAeYong",
    "category": "dev",
    "pages": 1000,
    "price": 0,
    "published_date": "2023-07-13",
    "description": "temp data"
}

POST버튼을 누르게 되면 다음과 같은 화면이 나올 것이다.

데이터를 POST요청으로 전송하여 저장되었다. 

이제는 bid(Book id)를 통해서 정보를 검색해보자. 

주소를 .../book/1/로 바꾸면 된다. 

id가 1인 도서의 정보를 보여주는 화면이 나오게 된다.

우리는 이렇게 도서 정보에 대한 API를 간단하게나마 만들어봤다. 

이런식으로 DRF의 기본 페이지로 API 테스트를 할 수도 있지만, 

https://insomnia.rest/ 

 

The Collaborative API Development Platform

Leading Open Source API Development Platform for HTTP, REST, GraphQL, gRPC, SOAP, and WebSockets

insomnia.rest

이 페이지에서도 API 테스트를 할 수 있다. 

 


1.3 DRF 심화 개념 보충

1.3.1 DRF의 다양한 뷰

DRF에서 기본적인 기능으로는 크게 다섯가지가 있다.

  1. List, 전체 목록 가져오기
  2. Create, 정보 등록하기
  3. Retrieve, 하나의 정보 가져오기
  4. Update, 정보 수정하기
  5. Destroy, 정보 삭제하기

여기서 객체 하나의 정보를 가져오는 것Retrieve라고 하며, 이는 DRF외에서도 통용적으로 쓰는 말이니 잘 알아두는 것이 좋다.

이것을 구현하는 것은 mixins, generics, Viewset까지 여러 단계를 걸쳐 발전해왔다. 

하나하나 다 설명할 필요 없이, 바로 Viewset을 보겠다. 

from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

viewsets를 import 하여 위와 같이 간단하게 4줄로 view를 작성할 수 있다. 

queryset과 serializer_class를 설정함으로써 CRUD를 위한 REST API가 완성되었다. 

어떻게 이런 일이 가능한걸까?

이것은 viewsets.ModelViewSet이 mixins의 기능들을 사용하여 자동으로 처리해주었기 때문이다. 

 

url과 연결할 때는 다음과 같이 router를 사용한다. 

from .views import BookViewSet
from rest_framework import routers

router = routers.SimpleRouter()
router.register('books', BookViewSet)

urlpatterns = router.urls

먼저 라우터 객체를 만들고, 여기에 우리가 만든 view를 등록하였다. 

이후 router.urls를 urlpatterns로 등록하여 직관적이고 간단하게 url을 등록할 수 있다. 

 

viewset과 router를 사용함으로써 생기는 장단점은 다음과 같다. 

장점 단점
겹치는 부분을 최소화한다. 개발자의 자유도가 낮아진다.
url을 일일이 지정하지 않아도 작성 가능하다. 커스텀이나 수정할 때 불편하다. 

 

'BE > DRF' 카테고리의 다른 글

2. TodoList API 작성  (0) 2023.07.21