LoginSignup
9
12

More than 3 years have passed since last update.

Dockerを用いてNuxt.js & Djangoで実装【AxiosでCRUD】

Last updated at Posted at 2021-01-06

はじめに

本記事へのアクセスありがとうございます。
投稿主はプログラミング初心者であり、この方法が「最適解」かは分かりません。

今度お仕事させて頂く実務でフロントにNuxt、バックエンドにDjangoを用いるので、全体の流れを少しでも把握するために本記事を作成しました。
同様な記事は環境構築までは確認できたのですが、せっかくならCRUD処理まで実装しようと思います。

*今回のNuxtの設定はSPAで行っていきます。時間があったらSSRバージョンも執筆します。
*DBはMySQLを使用します。

この記事から得られるものは?

・Dockerを用いたNuxtとDjangoの環境構築
・上記の環境でAxiosでCRUD実装(get,post,put,delete 全て扱っている記事は少ない)

さっそくスタート

Dockerを用いた環境構築はこちらの記事を参考にさせて頂きました。
環境構築完了イメージです。
スクリーンショット 2021-01-06 16.07.03.png

ディレクトリ構造

.
├─django
│  ├─manage.py
│  ├─qiitaexample
│  │   ├─settings.py
│  │   ├─urls.py
│  │   ├─wsgi.py
│  │   └─__init__.py
│  └─myapp
│      ├─migrations
│      ├─admin.py
│      ├─apps.py
│      ├─models.py
│      ├─renderers.py
│      ├─serializers.py
│      ├─tests.py
│      ├─urls.py
│      ├─views.py
│      └─__init__.py
│
├─docker-compose.yml
│
├─dockerfiles
│  ├─django_docker
│  │   ├─dockerfile
│  │   └─requirements.txt
│  └─nuxt_docker
│      └─dockerfile
│
├─mysql
│  └─conf.d
│
└─nuxt
    └─front
       └─以下略

初期段階で必要なファイル内容

docker-compose.yml

docker-compose.yml
version: '3'

services:
  db:
    image: mysql:latest
    restart: always
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: test
      MYSQL_USER: test
      MYSQL_DATABASE: test
      MYSQL_PASSWORD: test
    ports:
      - 3306:3306
    expose:
      - 3306
    volumes:
      - mysqldata:/var/lib/mysql
      - ./mysql/conf.d:/etc/mysql/conf.d
    command: --default-authentication-plugin=mysql_native_password

  web:
    container_name: django
    build: ./dockerfiles/django_docker
    command:
      python3 manage.py runserver 0.0.0.0:8000
    volumes:
      - ./django:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

  front:
    container_name: nuxt
    build: ./dockerfiles/nuxt_docker
    tty: true
    volumes:
      - ./nuxt:/code
    ports:
      - "3000:3000"

volumes:
  mysqldata:

dockerfiles/nuxt_doceker/dockerfile

FROM node:latest

RUN mkdir -p /code

ENV NODE_ENV=development

RUN yarn install
RUN yarn add @nuxtjs/axios

WORKDIR /code

EXPOSE 3000

dockerfiles/django_doceker/dockerfile

FROM python:3.7
ENV PYTHONUNBUFFERED 1

RUN mkdir /code
WORKDIR /code

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

COPY . /code/

dockerfiles/django_doceker/requriements.txt

requriements.txt
Django
djangorestframework
django-webpack-loader
django-cors-headers
mysqlclient

Djangoの設定

プロジェクトの作成

docker-composeでdbコンテナを立ち上げSqliteからMySQLの設定へと変更していきます。

> docker-compose up -d db

次にdocker-composeでコンテナを立ち上げます。初回なのでbuildも行っていきます。

> docker-compose up -d --build

Djangoのプロジェクトを作ります。(プロジェクト名は任意です)

> docker-compose run web django-admin.py startproject qiitaexample .

続いて、setting.pyを編集していきます。

#<DATABASESの'default'内を書き換える>

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test',
        'USER': 'test',
        'PASSWORD': 'test',
        'HOST': 'db',   
        'PORT': '3306',
    }
}

DATABASES変更後にDBのマイグレーションを行います。

> docker-compose run web ./manage.py migrate

続いてsuperuserを作成します。

> docker-compose run web ./manage.py createsuperuser --username admin --email admin@localhost
Password: #パスワードを聞かれるので入力
Password (again): #再度入力
The password is too similar to the username.
This password is too short. It must contain at least 8 characters.
This password is too common. #パスワードが短いと警告が出る
Bypass password validation and create user anyway? [y/N]: y #yを入力
Superuser created successfully.

docker-compose up -d コマンドコンテナを全て立ち上げ、正常に動作しているか挙動を確かめます。

> docker-compose up -d

> docker ps 

#3つのコンテナが動作しているはずです
・コンテナその1
・コンテナその2
・コンテナその3

django開発用サーバはポート8000で待ち受けているので、
http://localhost:8000/admin にアクセスしてみます。
表示された画面でUsername(ここではadmin)とpasswordを入力します。
すると、GroupsとUsersという2つの項目が表示されます。

アプリの作成

さて、次はアプリを作成していきます。(ここではmyappという名前で作成します)

> docker-compose run web ./manage.py startapp myapp

setting.pyにmyappをを追記して使用できるようにします。

/django/qiitaexample/setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp', #追加
]

models.pyにモデルを追記していきます。
ここではStudentというモデルを作成します。
models.pyを追記後、マイグレーションをすることでMySQL上にテーブルが作成されます。

/django/myapp/models.py
from django.db import models

class Student(models.Model):
    name = models.CharField(max_length=100)
    course = models.CharField(max_length=100)
    rating = models.IntegerField()

    def __str__(self):
      return self.name

    class Meta:
        ordering = ['name']

下記でマイグレーションを行います。
アプリを追加した場合はmakemigrationsが必要です。

> docker-compose run web ./manage.py makemigrations
> docker-compose run web ./manage.py migrate

admin.pyに下記を追記することで、管理画面から作成したモデルを確認できます。

/django/myapp/admin.py
from django.contrib import admin
from .models import Student

admin.site.register(Student)

再び http://localhost:8000/admin/ に接続するとMYAPPとStudentが増えていることが確認できます。
GUI操作で適当にStudentを登録し、MySQLのコンテナに入り確認してみます。

> docker exec -it mysql /bin/bash #MySQLコンテナに入る

> mysql -u test -p #MySQL起動
Enter password: #本記事の例だとtestでログイン可能

> use test;
> select * from myapp_student;

#確認できたら < shift + d > でコンテナから出る

Django REST frameworkの構築

Djangoの準備ができたところで、次はDjango側にAPIを作っていきます。
まずはsetting.pyに追記していきます。(Nuxtとの通信の際に必要な処理も予め記載しておきます)

/django/qiitaexample/setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp', 
    'rest_framework', #追加
    'corsheaders', #追加
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', #追加
    '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 = [
    'http://localhost:3000',
]

CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True

'rest_framework'を追記することで、Django REST frameworkの機能を使用できるようになります。
続いて、serializer.pyとurls.py(myapp配下)を手動で新規作成します。
作成後にserializer.py、urls.py、views.pyに下記内容を追記します

/django/myapp/serializers.py
from rest_framework.serializers import ModelSerializer
from .models import Student

class StudentSerializer(ModelSerializer):
  class Meta:
    model = Student
    fields = ['id' , 'name' ,'course' ,'rating']
/django/myapp/views.py
from django.shortcuts import render
from rest_framework.generics import ListAPIView, CreateAPIView,UpdateAPIView,RetrieveAPIView,DestroyAPIView
from rest_framework.permissions import AllowAny
from .models import Student
from .serializers import StudentSerializer

class StudentsViewSet(ListAPIView):
    model = Student
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
    permission_classes = (AllowAny, )

class StudentsDetailSet(RetrieveAPIView):
    model = Student
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
    permission_classes = (AllowAny, )

class StudentsCreateSet(CreateAPIView):
    model = Student
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
    permission_classes = (AllowAny, )

class StudentsUpdateSet(UpdateAPIView):
    model = Student
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
    permission_classes = (AllowAny, )

class StudentsDeleteSet(DestroyAPIView):
    model = Student
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
    permission_classes = (AllowAny, )

/django/myapp/urls.py
from django.urls import path
from .views import StudentsViewSet, StudentsCreateSet, StudentsUpdateSet,StudentsDetailSet,StudentsDeleteSet

urlpatterns = [
    path('get_myapp/', StudentsViewSet.as_view()),
    path('get_myapp/<int:pk>/', StudentsDetailSet.as_view()),
    path('post_myapp/', StudentsCreateSet.as_view()),
    path('put_myapp/<int:pk>/', StudentsUpdateSet.as_view()),
    path('delete_myapp/<int:pk>/', StudentsDeleteSet.as_view()),
]

qiitaexample配下のurls.pyを編集し、myapp配下のurls.pyを有効にします。

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

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

Nuxt.jsの設定

始めにnuxt.jsのコンテナに入り、プロジェクトを作成します(プロジェクト名は任意です)。

yarn create nuxt-app frontを入力後の選択肢は基本全てデフォルト値を入れていますが、
今回はSPAを選択しています。

> docker exec -it nuxt /bin/bash

> yarn create nuxt-app front

確認のために開発用サーバを動かす前に、nuxt.config.jsに追記します。
docker上でnuxt.jsを動かす際は下記が必要です。
また、ここから編集していく際にホットリロードを有効にします。
docker上で開発するためには明示的に指定が必要です。

さらにaxiosの設定を追記していきます。
axiosはnode.jsで動くHTTPクライアントで、これを利用してnuxt.jsから先ほどのAPIのURLを叩きます。
前述のnuxtのdockerfileに記載してあるので、すでにコンテナにはインストール済です。
modulesに下記を追記、及びaxiosを追加します。

/nuxt/front/nuxt.config.js
  server: {
    port: 3000,
    host: '0.0.0.0',  
  },

  watchers: {
    webpack: {
      poll: true
    }
  }


//~略~
  modules: [
    '@nuxtjs/axios',
  ],
  axios: {
    baseURL: "http://localhost:8000"
  },
//~略~

これでdocker上でNuxt.jsを表示させることができます

> cd front

> yarn run dev

無事Nuxt.jsの初期画面が現れればひとまず成功です。

AxiosでCRUD実装 Nuxt ⇆ Django

ここからは実際にNuxtとDjango間のデータの受け渡しについて記載していきます。
componentsの中にTestFormを新規作成します。

/nuxt/front/components/TestForm.vue
<template>
  <div>
    <div>
      <form @submit.prevent="submitForm(student)">
        <div class="from-group row">
          <input
            type="text"
            class="form-control col-3 mx-2"
            placeholder="Name"
            v-model="student.name"
          />
          <input
            type="text"
            class="form-control col-3 mx-2"
            placeholder="Course"
            v-model="student.course"
          />
          <input
            type="text"
            class="form-control col-3 mx-2"
            placeholder="Rating"
            v-model="student.rating"
          />
          <button class="btn btn-success">Submit</button>
        </div>
      </form>
    </div>
    <div>
      <table class="tabel">
        <thead>
          <th>Name</th>
          <th>Course</th>
          <th>Rating</th>
        </thead>
        <tbody>
          <tr
            v-for="student in students"
            :key="student.id"
            @dblclick="$data.student = student"
          >
            <td>{{ student.name }}</td>
            <td>{{ student.course }}</td>
            <td>{{ student.rating }}</td>
            <td><button @click="deleteStudent(student)">x</button></td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      student: {
        name: "",
        course: "",
        rating: "",
      },
      students: [],
    };
  },
  async created() {
    await this.getStudents();
  },
  methods: {
    submitForm(student) {
 //新規にPOSTする場合はcrete , 既存にデータがある場合はputメソッドを利用するように条件分岐
      if (this.student.id === undefined) {
        this.createStudent();
      } else {
        this.editStudent(student);
      }
    },
    //データを全権取得
    async getStudents() {
      const url = "/api/get_myapp/";
      const response = await this.$axios.get(url);
      this.students = response.data;
    },
    //データを新規登録
    async createStudent() {
      await this.getStudents();
      const url = "/api/post_myapp/";
      this.$axios.defaults.xsrfCookieName = "csrftoken";
      this.$axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
      await this.$axios.post(url, {
        name: this.student.name,
        course: this.student.course,
        rating: this.student.rating,
      });
      await this.getStudents();
    },
   //データを編集
    async editStudent(student) {
      await this.getStudents();
      const url = "/api/put_myapp/" + student.id + "/";
      const modify = {
        name: this.student.name,
        course: this.student.course,
        rating: this.student.rating,
      };
      await this.$axios.put(url, modify);
      await this.getStudents();
      this.student = {};
    },
   //データを削除
    async deleteStudent(student) {
      await this.getStudents();
      const url = "/api/delete_myapp/" + student.id + "/";
      const modify = {
        name: this.student.name,
        course: this.student.course,
        rating: this.student.rating,
      };
      await this.$axios.delete(url)
      await this.getStudents();
    },
  },
};
</script>

作成したTestFormを読みます。
不必要なら、template内の初期表示部分を削除してしまって構いません。

/nuxt/front/pages/index.vue
//追記
  <div>
      <TestForm />
   </div>

--------------------
<script>
import TestForm from '~/components/TestForm.vue'
export default {
  components:{
    TestForm
  }
}
</script>

これでAixosでCRUD処理のコードが完成です。
最後に全てのコンテナを立ち上げて、Nuxtのコンテナでyarn run devして確認してみましょう。

> docker-compose up -d

> docker exec -it nuxt  /bin/bash

> cd front

> yarn run dev

おわりに

お疲れ様でした。
以上でNuxt.js ⇆ Djangoのデータの連携が完成です。
個人的にはAxiosでputする時に以下のコードをurls.pyに書き忘れていて、ずっと404not foundで言われて少し苦戦しました。
解決してみれば確かに、詳細ページのurl設定を行っていないのでページが表示(一つ単位の情報を取得)できるはずもありませんでした。

path('get_myapp/<int:pk>/', StudentsDetailSet.as_view()),

エラーコードを読みどこがいけないのか判断、予測して実装しなければ。と改めて思いました。

余談ではありますが、DRFの理解の際に役に立ったリンクを紹介します。
Django REST framework カスタマイズ方法 - チュートリアルの補足
DRFのGeneric viewの使い方

少しでもみなさんのお役に立てたら嬉しいです。

9
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
12