Django REST framework + Nuxt.js で開発するときのメモ


この記事では、Django REST framework と Nuxt.js を組み合わせた開発環境の整備に取り組みます。
Django 側、Nuxt 側双方でホットリロードが効くような環境を整備することが目標です。
DB として、この記事では PostgreSQL を使います。Django と合わせて Docker 環境で用意します。一方、フロントの Nuxt はコンテナ上で動かすと IO が遅いため、ホスト側で直接動かすことにします。

バックエンドの設定

ファイルを以下のように用意します。

.
├── backend
│   ├── Dockerfile
│   └── requirements.txt
└── docker-compose.yml
backend/Dockerfile
FROM python:3.9
WORKDIR /usr/src/app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

COPY ./requirements.txt .
RUN pip install -r requirements.txt

COPY . .
backend/requirements.txt
django==3.2
djangorestframework
psycopg2>=2.8
docker-compose.yml
version: "3.9"
   
services:
  django:
    build: ./backend
    command: python manage.py runserver
    volumes:
      - ./backend:/usr/src/app/
    ports:
      - "8000:8000"
    environment:
      - POSTGRES_NAME=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    depends_on:
      - db

  db:
    image: postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres

volumes:
  postgres_data:

プロジェクトを作成します。

docker-compose run django django-admin startproject mainproject .

アプリケーションを作成します。

docker-compose run django django-admin startapp mainapp

Django の設定を変更します。

backend/mainproject/settings.py
 from pathlib import Path
+import os
 
 # Build paths inside the project like this: BASE_DIR /  'subdir'.
 BASE_DIR = Path(__file__).resolve().parent.parent
backend/mainproject/settings.py
 DATABASES = {
     'default': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': BASE_DIR / 'db.sqlite3',
+        'ENGINE': 'django.db.backends.postgresql',
+        'NAME': os.environ.get('POSTGRES_NAME'),
+        'USER': os.environ.get('POSTGRES_USER'),
+        'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
+        'HOST': 'db',
+        'PORT': 5432,
     }
 }
backend/mainproject/settings.py
-LANGUAGE_CODE = 'en-us'
+LANGUAGE_CODE = 'ja'

-TIME_ZONE = 'UTC'
+TIME_ZONE = 'Asia/Tokyo'

そして、サーバーを立ち上げます。

docker-compose up --build

localhost:8000 で以下のような画面が表示されることを確認します。

Ctrl + C でサーバーを停止します。
DB の初期マイグレーションを行います。

docker-compose run django python manage.py migrate

Admin ユーザを作成します。

docker-compose run django python manage.py createsuperuser --email [email protected] --username admin

この後、サーバーを立ち上げて、管理画面 http://localhost:8000/admin にアクセスできることを確認します。

次に Django REST Framework の機能を試します。

モデルを作成します。

backend/mainapp/models.py
from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

モデルに対応した Serializer, ViewSet を実装します。

backend/mainapp/serializers.py
from mainapp.models import Question, Choice
from rest_framework import serializers


class QuestionSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Question
        fields = ['question_text', 'pub_date']


class ChoiceSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Choice
        fields = ['question', 'choice_text', 'votes']
backend/mainapp/views.py
from mainapp.models import Question, Choice
from rest_framework import viewsets
# from rest_framework import permissions
from mainapp.serializers import QuestionSerializer, ChoiceSerializer


class QuestionViewSet(viewsets.ModelViewSet):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer
    # permission_classes = [permissions.IsAuthenticated]


class ChoiceViewSet(viewsets.ModelViewSet):
    queryset = Choice.objects.all()
    serializer_class = ChoiceSerializer
    # permission_classes = [permissions.IsAuthenticated]

実装した REST API を URI に含めます。

backend/mainproject/urls.py
from django.urls import include, path
from rest_framework import routers
from mainapp import views

router = routers.DefaultRouter()
router.register(r'questions', views.QuestionViewSet)
router.register(r'choices', views.ChoiceViewSet)

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
    path('', include(router.urls)),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]

Django の設定に変更を加えます。

backend/mainproject/settings.py
 INSTALLED_APPS = [
+    'mainapp.apps.MainappConfig',
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
+    'rest_framework',
 ]

モデルを新しく追加したので、マイグレーションを行う必要があります。

docker-compose run django python manage.py makemigrations
docker-compose run django python manage.py migrate

そうしたら、サーバーを起動して、http://localhost:8000/questionshttp://localhost:8000/choices などにアクセスして、データの中身を操作できることを確認します。

バックエンドとフロントエンドは異なる Origin(ホスト・ポート・プロトコルの組み合わせ)なので、後でフロントエンド側から API を操作できるように、CORS の設定をします。

backend/requirements.txt
 django==3.2
 djangorestframework
+django-cors-headers
 psycopg2>=2.8
backend/mainproject/settings.py
 INSTALLED_APPS = [
     'mainapp.apps.MainappConfig',
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'rest_framework',
+    "corsheaders",
 ]
 
 MIDDLEWARE = [
     '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',
+    "corsheaders.middleware.CorsMiddleware",
 ]
+
+CORS_ALLOWED_ORIGINS = [
+    "http://localhost:3000",
+    "http://127.0.0.1:3000",
+]

フロントエンドの設定

プロジェクトを開始します。

yarn create nuxt-app frontend

ひとまず以下のようにしてみました。

Project name: frontend
Programming language: TypeScript
Package manager: Yarn
UI framework: Tailwind CSS
Nuxt.js modules: Axios - Promise based HTTP client
Linting tools: ESLint, Prettier
Testing framework: Jest
Rendering mode: Universal (SSR / SSG)
Deployment target: Static (Static/Jamstack hosting)
Development tools: None
Continuous integration: None
Version control system: None

インストールが終わったら、

cd frontend
yarn dev

でサーバーを立ち上げ、localhost:3000 でアクセスできることを確認しましよう。

API の baseURL として http://localhost:8000 を使います。

frontend/nuxt.config.js
 axios: {
-  baseURL: '/',
+  baseURL: 'http://localhost:8000/',
 },

先ほど /questions という API を用意したので、それを叩いて中身を取得するようなページを作成してみたいと思います。

frontend/pages/questions.vue
<template>
  <div>
    <h1 text-4xl>質問一覧</h1>
    <ul>
      <li v-for="(question, key) in questions" :key="key">{{ question }}</li>
    </ul>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        questions: []
      }
    },
    async asyncData({ $axios }) {
      const questions = await $axios.$get('questions/')
      return {
        questions
      }
    },
    head() {
      return {
        title: '質問一覧'
      }
    }
  }
</script>

これで http://localhost:3000/questions/ を開くと、以下のようにデータをうまく取得することが確認できます。

結局色々書いたけど...

下のテンプレートを使った方がよさそう()

https://github.com/naritotakizawa/docker-drf-and-nuxt-template

このテンプレートをさらにちょっと修正して、最終版テンプレートができあがりました。

https://github.com/HelloRusk/docker-django-nuxt-nginx-template

References