규도자 개발 블로그

간단한 react JS + Django 어플리케이션 만들기 본문

Python/Django

간단한 react JS + Django 어플리케이션 만들기

규도자 (gyudoza) 2018. 12. 22. 14:50

간단한 react JS + Django 어플리케이션 만들기

일단 프로젝트를 시작하기 전에 서론이 길다. react와 django가 동작하는 방식에 대해선 관심 없고 단지 어떻게 만드는 지에 대해서만 관심있는 사람이라면 이 텍스트블록들은 과감하게 넘겨도 좋다. 하지만 제대로 만들기 위해선 react와 django를 이용한 웹어플리케이션이 어떻게 작동하는지에 대해서 간단하게나마 알아둘 필요가 있다. 그럼 시작하겠다.

react는 Single Page Application을 위해 만들어진 Javascript라이브러리이다. 페이스북에서 만들었는데 딱 페이스북이 어떻게 동작하는지 보면 이 용도를 이해할 수 있다. 페이스북에서 좋아요를 누르고 댓글을 달고 페메를 보내는 건 전부 한 페이지에서 이뤄진다. 넷플릭스 또한 어떤 작품을 누르든 장르 범주를 바꾸지 않는 이상 해당 페이지에서 계속해서 데이터가 바뀌고 DOM구조가 바뀐다. 지속적인 데이터 변화 및 구조가 변경되는 페이지를 전부 ajax와 js, jQuery만으로 구현하면 코드가 어지러워지고 또 이는 유지보수의 어려움을 야기하기 때문에 react가 나온 것이다. 그리고 이 react는 jsx라는 문법을 사용한다. 필수는 아니지만 이를 사용하면 코드 생산성과 퍼포먼스를 높일 수 있다.

 django는 python언어를 기반으로 한 경량 웹프레임워크이다. 더 할 말이 없다. 아무튼 서로 다른 언어로 만들어진 웹프레임워크와 라이브러리가 만나 하나의 서비스로 작동해야 한다.


첫번째 방법


첫번째 방법으로는 django template에서 react가 작동할 수 있게 static파일에 react js를 넣어 라이브러리로 사용하거나 cdns서버에서 라이브러리를 가져오는 방법이다. 이 방법은 서버 스트럭쳐가 간단하지만 react의 기능 사용에 제한이 많아 많이 채택되는 방법은 아니다.



두번째 방법


두번째 방법으로는 frontend를 전부 react로 작성하고 그 데이터는 내부통신망의 django-rest-framework를 이용하여 가져오는 방식이다. 외부에는 front로 작성된 부분만 노출되고 동작이 이뤄질 때마다 내부에 개설된 django-rest-framework 네트워크를 통해 가져온다고 보면 된다. 그래서 비즈니스로직과 프론트엔드 동작을 완전히 분리할 수 있고 react의 기능도 온전히 사용가능할 수 있다. 하지만 서버 설정이 첫번째 방법보다는 복잡하다. django는 uWSGI나 gunicorn을 통해 항상 rest-api request에 응답할 수 있는 상태가 돼야하고 외부에는 react로 만들어진 frontend가 노출돼있어야한다. 사실상 모든 CRUD작업이 django-rest-framework에 의해서 이뤄지므로 GET, POST, PUT, DELETE작업에 대한 인증 작업 및 구분을 제대로 구현해야 한다. 한줄요약하자면 django와 react사이의 내부네트워크를 구성한 다음에 react와 client의 외부네트워크를 구성시켜야 한다. 아 물론 꼭 django와 react사이에 내부네트워크가 존재해야하는 건 아니다. 인증과정과 CRUD동작에 대한 rest api루트가 제대로 설정돼있다면 frontend서버와 backend서버를 완전분리시키는 것도 좋은 방법일 것이다.

 하지만 두번째 방법 사용시 알아야할 것은 여기에서 끝이 아니다. 바로 SEO를 위한 SSR이다. 무슨 말인가 하면 검색엔진최적화(Search Engine Optimization)를 위해서 서버사이드렌더링(Server Side Rendering)이 돼야한다는 말이다. 한 예로 첫번째 방법과 비교해보자. 첫번째 방법은 사이트에 들어가자마자 http리퀘스트에 의해 html페이지가 완성되어 돌아온다. 그렇게 나온 데이터를 검색엔진이 종합하여 노출순위를 지정한다. 하지만 두번째 방법 사용시에는 그냥 http요청만으로는 페이지가 그려서 오지 않는다. 페이지 안에 들어가서 작성돼있는 javaScript들을 자극해야만 해당 정보들이 html태그로 그려져서 출력된다.
 그렇다, 검색엔진은 http요청을 보낸 순간에 그려져있지 않는 비동기데이터들은 종합하지 못한다. 그래서 SSR이라는 기술이 필요한 것이다. http요청을 받았을 때 이미 어느정도는 html태그로 페이지를 그려놓아 검색엔진이 정보를 수집해갈 수 있게 해주는 기술인 것이다. 구글의 검색엔진이 js를 작동하여 종합한 데이터까지 포함시키게 개선할 거라는 얘기는 종종 있다. 구현 된건지, 아니면 개발 중인지는 모르겠다.


종합


많은 사이트들은 두번째 방식을 선호한다. SSR같은 경우에는 어차피 기술로 구현할 수 있을 뿐더러 frontend와 backend의 완전한 분리가 유지보수성 및 작업효율 향상에 지대한 영향을 끼치기 때문이다. backend프로그래머들은 데이터에만 신경쓰면 되고 frontend프로그래머들은 어떻게 보여지는지에 대한 신경만 쓰면 된다. 그래서 서론이 길었지만 한번 django와 react를 이용하여 간단한 어플리케이션을 만들어보겠다.


준비물


  • python3.x
  • django
  • djangorestframework
  • npm
  • yarn

파이썬은 파이썬 공식홈페이지에서 설치할 수 있고 django와 djangorestframework는 진행하는 도중에 venv를 이용해 깔 것이다. npm과 yarn이 설치돼있지 않다면 이 게시물을 참고하길 바란다. 커맨드창에 이런 결과가 나오면 준비가 완료된 것이다. 그리고 모든 과정은 윈도우10에서 진행됐다는 점을 알린다.



django 구축하기


일단 작업 디렉토리를 정하자. 나는 디렉토리 이름을 django-react로 정하였다. 그리고 그 안에 django-react-venv라는 이름으로 가상환경을 설정하자. 가상환경 설정이 끝났으면 backend디렉토리를 생성하고 django를 설치하자. 프로젝트명은 djangoreactapi로 하였다. 명령어로 정리하자면 이렇다. 특히 프로젝트를 시작할 때 뒤의 .을 잊지 말고 새로운 폴더가 아닌 현재 디렉토리에서 프로젝트를 생성할 수 있게 하자. (몇몇 예약어는 프로젝트명으로 사용하라 수 없다.)

C:\~\django-react> python3 -m venv django-react-venv
#가상환경 생성
C:\~\django-react> django-react-venv\Scripts\activate
#가상환경 실행
C:\~\django-react> python -m pip install --upgrade pip
#pip업데이트. 현재는 가상환경 안에 있으므로 그냥 python명령어가 python3를 가리킨다.
C:\~\django-react> pip install django
#django 설치. django-rest-framework는 차후에 설치한다.
C:\~\django-react> mkdir backend
C:\~\django-react> cd backend
C:\~\django-react\backend> django-admin startproject djangoreactapi .

여기까지 진행했을 때의 디렉토리 구조는 이렇다.

이번엔 api로 호출시킬 app을 만들자. manage.py가 있는 backend디렉토리에서 아래 명령어를 실행하면 된다. 나는 api로 호출할 앱의 이름을 post로 하였다.

C:\~\django-react\backend> python manage.py startapp post
#app초기화
C:\~\django-react\backend> python manage.py migrate
#migrate실행. DB를 따로 설정하지 않았기 때문에 sqlite파일이 생성될 것이다.

post앱이 추가되었으니 djangoreactapi/settings.py에 post를 추가하자.

#backend/djangoreactapi/settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'post', #추가
    
]
...

그리고 django의 개발용 내부 웹서버를 실행해보자.

C:\~\django-react\backend> python manage.py runserver


아주 잘 작동한다. 이제 모델을 정의하고 제대로 app을 활용할 수 있게 하자.

#backend/post/models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()

    def __str__(self):
        """A string representation of the model."""
        return self.title
#backend/post/admin.py
from django.contrib import admin

from .models import Post

admin.site.register(Post)

여기까지 했으면 migrate으로 변경된 부분을 db에 적용시키고 superuser를 생성하여 제대로 작성됐는지 확인해보자. manage.py가 있는 backend폴더로 돌아가서 migrate를 실행시키고 관리자를 생성했으면 다시 runserver를 통해 제대로 작동하는지 확인해보자.

C:\~\django-react\backend> python manage.py makemigrations
...
C:\~\django-react\backend> python manage.py migrate
...
C:\~\django-react\backend> python manage.py createsuperuser
...
C:\~\django-react\backend> python manage.py runserver

이 과정까지 완료했을 때 localhost에서 돌아가고 있는 웹서버 뒤에 /admin이라는 슬러그를 추가하여 접속하고 createsuperuser로 만든 계정으로 로그인하자. 이 과정까지 잘 이뤄졌으면 아래 모습을 확인할 수 있다.

임시로 여러개의 포스트를 추가하자. UI가 직관적이라 햇갈리진 않을 것이다. Add버튼을 통해 마음 내키는대로 추가하자. 난 이런식으로 추가하였다.

여기까지 했으면 django-rest-framework를 사용할 차례이다.



django-rest-framework 구성하기


아래의 명령어를 통해 django-rest-framework를 설치하자. 사담이지만 DRF는 진짜 사용해보면 해볼수록 멋지고 잘 만든 라이브러리인 것 같다.

C:\~\django-react\backend> pip install djangorestframework

설치가 완료되면 아까처럼 settings.py에 새로운 app이 추가됐다는 것을 알려줘야 한다.

#backend/djangoreactapi/settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'post',
    'rest_framework', #추가
    
]
...
#이하 추가
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
}

참고해야할게 DRF는 자동으로 pk값을 index로 지정해서 뿌려준다는 점이다. 이제 api app을 추가하였으니 api루트로 들어왔을 때 데이터를 보낼 수 있게 해야한다. 어떤 형식의 데이터를 보낼지는 post app디렉토리 안의 view.py에서 정해진다. 그리고 중요한게 바로 serializers이다.


django + react 앱은 api요청을 통해 데이터를 주고 받는데 많은 사람들이 아다시피 api요청 및 반환값은 거진 데이터포멧이 JSON(JavaScript Object Notation)으로 되어있다. 그래서 반환값을 JSON으로 직렬화(Serialize)해주는 것이 필요하다. 이때 필요한 것이 DRF의 serializers이다. 만들어져있는 파일이 아니니 직접 만들어 작성해야한다.

#backend/post/serializers.py
from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        fields = (
            'id',
            'title',
            'content',
        )
        model = Post

seializer가 작성되었으면 views.py를 작성하자.

#backend/post/views.py
from django.shortcuts import render
from rest_framework import generics

from .models import Post
from .serializers import PostSerializer

class ListPost(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

class DetailPost(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

이제 데이터를 다듬어서 보내줄 준비는 되었다. 이제 api요청이 왔을 때 Post데이터를 보내야 하니 urls.py를 만들어서 작성하자.

#backend/post/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.ListPost.as_view()),
    path('<int:pk>/', views.DetailPost.as_view()),
]

Post내부의 urls.py를 정의하였으니 이제 루트 디렉토리에서의 urls.py에 이 내용을 반영시켜야 한다.

#backend/djangoreactapi/urls.py
from django.contrib import admin
from django.urls import path, include

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

여기까지 작성되었으면 이제 api요청이 제대로 작동하는지 확인해보자. manage.py가 있는 디렉토리에서 runserver를 실행하여 다시 서버를 띄워보자. 그리고 뒤에 /api슬러그를 붙여 접속해보면 아래 화면을 확인해볼 수 있다.

아까 /admin에서 등록했던 내용들이다. 주소 뒤에 id를 지정해주면 post/urls.py에 선언해놨던 url패턴에 따라서 해당 Post만 조회할 수 있다.

이런식으로 말이다. 여기까지 왔으면 이제 django api서버의 준비는 완료된 것이다. 그리고 마지막으로 script태그 안에서의 api를 통한 데이터의 접근제어를 위해 HTTP 접근제어 규약(CORS : Cross-Origin Resource Sharing)을 추가해야한다. 간단하게 설명하자면 기존의 HTTP요청에서는 imglink태그 등으로 다른 호스트의 css나 이미지파일 등의 리소스를 가져오는 것이 가능한데 script태그로 쌓여진 코드에서의 다른 도메인에 대한 요청은 Same-origin policy에 대한 규약으로 인해 접근이 불가하다. 하지만 아다시피 react는 거의가 script요청에 의해 페이지를 그리는 방식이므로 이제 대한 제약 해제가 필요하다. django로 돌아가고 있는 api서버와 페이지를 그려주는 react서버는 명목상 아예 다른 서버로 구분되기 때문이다.


말만 들으면 굉장히 어렵운 작업일 것 같지만 패키지 하나를 설치하여 추가함으로써 해결할 수 있다.

C:\~\django-react\backend> pip install django-cors-headers
#backend/djangoreactapi/settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'post',
    'rest_framework',
    'corsheaders', # 추가
]
...
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',     # 추가
    'django.middleware.common.CommonMiddleware', # 추가
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
...
CORS_ORIGIN_WHITELIST = (
    'localhost:3000/'
)
#script안에서의 리소스 요청을 허용할 도메인 추가

이제 간단한 react앱을 작성하여 django에서 전송해주는 데이터를 받아보도록 하자.



REACT 앱 작성하기


준비물에서 말했던 yarn을 기반으로 작업할 것이다. 작업 초반에 backend디렉토리를 만들었던 django-react디렉토리로 돌아가서 create-react-app을 통해서 react앱을 작성할 수 있는 환경을 조성할 것이다. 일단 그전에 create-react-app이 필요하다. yarn이나 npm으로 설치할 수 있는 패키지이다. 다음의 명령어를 통해서 설치할 수 있다.

npm install -g create-react-app
#npm을 이용한 방법
yarn global add create-react-app
#yarn을 이용한 방법

create-react-app설치가 완료됐으면 django-react 디렉토리로 이동해 작업하자.

C:\~\django-react> create-react-app frontend

초기화가 완료됐으면 yarn start명령어를 통해 react app이 제대로 작동하는지 확인해보자.

C:\~\django-react> cd frontend
C:\~\django-react\frontend> yarn start

제대로 실행되면 localhost의 3000번 포트에서 react가 실행된 화면이 뜰 것이다.

이렇게 말이다.


이제부터는 frontend요청을 처리할 웹서버와 backend api요청을 처리할 두 개의 웹서버가 작동돼야한다. 아마 이 react앱을 만드는 과정에서 django웹서버가 중지됐을 것이다. 내부 웹서버를 실행하면 cli가 http요청을 출력해주는 형태로 freeze되기 때문이다. venv를 사용하고 있는 커맨드 창에서는 아래의 명령어를 입력하고

C:\~\django-react\backend> python manage.py runserver

새로운 커맨드창을 켜서 아래의 명령어를 입력하자.

C:\~\django-react\frontend> yarn start

그리고 이제는 react앱에서 django가 보내주는 데이터를 받아볼 차례이다. create-react-app 명령어로 생성된 frontend디렉토리에서 src/app.js를 아래와 같이 수정하자.

//frontend/src/app.js
import React, { Component } from 'react';

class App extends Component {
    state = {
        posts: []
    };

    async componentDidMount() {
        try {
            const res = await fetch('http://127.0.0.1:8000/api/');
            const posts = await res.json();
            this.setState({
                posts
            });
        } catch (e) {
            console.log(e);
        }
    }

    render() {
        return (
            <div>
                {this.state.posts.map(item => (
                    <div key={item.id}>
                        <h1>{item.title}</h1>
                        <span>{item.content}</span>
                    </div>
                ))}
            </div>
        );
    }
}

export default App;

조금은 생소한 문법일 것이다. 이에 대해선 jsx를 알아보면 되겠다. 아무튼 이렇게 입력을 끝내면 yarn start로 실행된 웹화면에서 다음과 같은 데이터들을 확인할 수 있다.

django admin페이지에서 내가 임의로 넣었던 데이터들이다. 여기까지 완료됐을 때의 디렉토리 구조는 아래와 같다.


이렇게 django와 react가 연동된 앱을 만들어보았다. django api서버를 내부에 넣어서 때로는 django template으로 만들어진 페이지를 제공하다가 필요할 땐 react로 만들어진 SPA를 제공하거나 혹은 django api서버와 frontend서버를 완전 분리하여 운영하는 것도 방법이겠다.

 

Comments