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
FROM python:3.9
WORKDIR /usr/src/app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
COPY ./requirements.txt .
RUN pip install -r requirements.txt
COPY . .
django==3.2
djangorestframework
psycopg2>=2.8
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 の設定を変更します。
from pathlib import Path
+import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
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,
}
}
-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 admin@example.com --username admin
この後、サーバーを立ち上げて、管理画面 http://localhost:8000/admin
にアクセスできることを確認します。
次に Django REST Framework の機能を試します。
モデルを作成します。
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 を実装します。
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']
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 に含めます。
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 の設定に変更を加えます。
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/questions
や http://localhost:8000/choices
などにアクセスして、データの中身を操作できることを確認します。
バックエンドとフロントエンドは異なる Origin(ホスト・ポート・プロトコルの組み合わせ)なので、後でフロントエンド側から API を操作できるように、CORS の設定をします。
django==3.2
djangorestframework
+django-cors-headers
psycopg2>=2.8
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
を使います。
axios: {
- baseURL: '/',
+ baseURL: 'http://localhost:8000/',
},
先ほど /questions
という API を用意したので、それを叩いて中身を取得するようなページを作成してみたいと思います。
<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/
を開くと、以下のようにデータをうまく取得することが確認できます。
結局色々書いたけど...
下のテンプレートを使った方がよさそう()
このテンプレートをさらにちょっと修正して、最終版テンプレートができあがりました。
Discussion