フロントNuxt.js - バックDjango間のAPI連携を実装してみた

# 初めに
初投稿です。
最近Web系の開発に興味があり、色々勉強しています。
この記事では、バックエンドの部分をDjangoで、
フロントエンドの部分をNuxt.jsで作成したものについて、
とりあえず開発環境で動くもので書いていきます。

# 準備
下記イメージを元にdocker-composeを使ってそれぞれの環境を構築
![qiita用1 (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386475/9e399cec-ee38-b4e0-93ef-416279b31958.jpeg)

###環境
Window10 Pro
Docker desktop v2.1.0.5
Python 3.6.9
django 2.2.7
djangorestframework 3.10.3
MySQL Ver 8.0.17
Nuxt.js v2.11.0

###ディレクトリ構造

```
.
├─django
│  ├─manage.py
│  ├─composeexample
│  │   ├─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
       └─以下略
```

###各種ファイル


```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

  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:
```

```dockerfile:./dockerfiles/django_docker/dockerfile
# ベースイメージを指定
FROM python:3.6-stretch

ENV PYTHONUNBUFFERED 1
RUN mkdir /code

# ディレクトリを移動する
WORKDIR /code

# pipでrequirements.txtに記載のパッケージをインストール
COPY requirements.txt /code/
RUN pip3 install -r requirements.txt

COPY . /code/
```
※djangoのイメージはOpen SSHの鍵長の関係でstretchでbuildしてます。[こちら参照](https://tech.bank.co.jp/entry/2019/07/29/170000)

```txt:./dockerfiles/django_docker/requirement.txt
Django>=2.0,<3.0
djangorestframework
django-webpack-loader
django-cors-headers
mysqlclient
```
※requierment.txtに必要となるpythonのパッケージを記載します。
　Djangoだけでなく、今回APIに必要なパッケージも入れます。

　以下はDjango REST frameworkに必要なもの
__　・djangorestframework__
__　・django-webpack-loader__
__　・django-cors-headers__

　また以下はMySQLをDjangoから使うのに必要なもの
__　・mysqlclient__

```dockerfile:./dockerfiles/nuxt_docker/dockerfile
# ベースイメージを指定
FROM node:latest

RUN mkdir -p /code

# node.js の環境変数を定義する
# 本番環境では production
ENV NODE_ENV=development

# yarnとaxiosをinstall
RUN yarn install
RUN yarn add @nuxtjs/axios

# ディレクトリを移動する
WORKDIR /code

# ポート3000番を開放する
EXPOSE 3000
```

#djangoの構築
###プロジェクトの作成
こちらの記事を参考にしてます。
[Djangoを最速でマスターする part1](https://qiita.com/gragragrao/items/373057783ba8856124f3)
[DockerでDjangoの開発環境を作成する（Quickstart: Compose and DjangoのMySQL版）](https://aoishi.hateblo.jp/entry/2017/11/05/153341)
[【Django】アプリケーションの追加手順](https://qiita.com/SATOSHI-G/items/8ae2671541d6d1a4da06)
まずdocker-composeでコンテナを立ち上げます。MySQLから建てます。

```bash:
> docker-compose up -d db
```
docker ps で確認

```bash:
> docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
285cf6b8473b        mysql:latest        "docker-entrypoint.s…"   2 minutes ago       Up 4 seconds        0.0.0.0:3306->3306/tcp, 33060/tcp   mysql
```

続いてdocker-composeでコンテナを立ち上げます。初回なのでbuildもしておきます。

```bash:
> docker-compose up -d --build
```

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

```bash:
> docker-compose run web django-admin.py startproject composeexample .
```

この部分が生成されます。

```
.
└─django
   ├─manage.py
   └─composeexample
       ├─settings.py
       ├─urls.py
       ├─wsgi.py
       └─__init__.py
```

続いてsetting.pyを編集します。
デフォルトではSQLiteを使用するようになっているので、これをMySQLに変更します。

```python:
#<変更前>
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

#上記を↓↓↓に変更
#ユーザ名やパスは適宜変更してください

#<変更後>
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test',
        'USER': 'test',
        'PASSWORD': 'test',
        'HOST': 'db',   
        'PORT': '3306',
    }
}

```

次は連携したDBのマイグレーションを行います。

```bash:
> docker-compose run web ./manage.py migrate
```

スーパユーザを作成します。

```bash:
> docker-compose run web ./manage.py createsuperuser --username admin --email admin@localhost
Starting mysql ... done                                                                                   
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.
```

ここで一度djangoの開発用サーバを起動してみます。
docker-compose up -dでdocker-compose.ymlに記載したコンテナが全てバックグラウンドで起動します。
(Nuxtのコンテナもこの段階で起動しています)

```bash:
> docker-compose up -d

> docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                         NAMES
746b29edf07b        working_web         "python3 manage.py r…"   30 hours ago        Up 30 hours         0.0.0.0:8000->8000/tcp                        django
0ae9f6506532        mysql:latest        "docker-entrypoint.s…"   30 hours ago        Up 30 hours         3306/tcp, 33060/tcp, 0.0.0.0:3306->3306/tcp   mysql
0107b2a7301b        working_front       "docker-entrypoint.s…"   30 hours ago        Up 30 hours         0.0.0.0:3000->3000/tcp                        nuxt
```

django開発用サーバはポート8000で待ち受けているので、
http://localhost:8000/admin にアクセスしてみます。
./docker-compose.ymlの下記に該当します。

```yml:./docker-compose.yml
#~略~
    command: 
      python3 manage.py runserver 0.0.0.0:8000
#~略~
```
下記画面が出れば成功です。(Chromeの拡張入れているので背景が黒いです)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386475/415b8e3e-0651-9926-819d-6ec27f533c1f.png)

作成したスーパユーザでログインしてみます。
デフォルトでは2つのユーザが作られていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386475/1051e92f-2d69-e21c-438b-a909f4ee881e.png)

###アプリの作成
続いてアプリ部分を生成します。(アプリ名は任意です)

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

この部分が生成されます。

```
.
└─django
   └─myapp
       ├─migrations
       ├─admin.py
       ├─apps.py
       ├─model.py
       ├─tests.py
       ├─view.py
       └─__init__.py
```

setting.pyに作成したアプリを追記します。
これをしないとdjango上で生成したアプリが認識されません。

```python:./django/composeexample/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にモデルを追記していきます。
とりあえず簡易なPerson_dataモデルを作成します。
人の名前と年齢のみを保持したものです。
ここに記載し、マイグレーションをすることでMySQL上にテーブルが作成されます。

```python:./django/myapp/models.py
from django.db import models

# Create your models here.
class Person_data(models.Model):
    person_name = models.TextField()
    person_age = models.IntegerField()
```

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

```bash:
> docker-compose run web ./manage.py makemigrations
> docker-compose run web ./manage.py migrate
```
admin.pyに下記を追記することで、管理画面から作成したモデルを確認できます。

```python:./django/myapp/admin.py
from django.contrib import admin
from .models import Person_data #作成したモデルをimport

# Register your models here.
admin.site.register(Person_data) #作成したモデルを追記
```
MYAPPとPerson_datasが追加されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386475/fa1ffbee-a780-abe9-c679-2b019a557177.png)

+ADDをクリックするとデータ追加画面が開きます。
SAVEで追加できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386475/db578d0d-9e31-349d-2f59-6809f2aacbe1.png)

適当にデータを追加していくと下記のように行が増えていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386475/a49245de-cf77-ad67-8fac-d5e779bd6a22.png)

下記でMySQL上でも確認できます。

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

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

> use test;
> select * from myapp_person_data;
+----+-------------+------------+
| id | person_name | person_age |
+----+-------------+------------+
|  1 | takashi     |         26 |
|  2 | naoto       |         32 |
|  3 | tomoko      |         15 |
+----+-------------+------------+
3 rows in set (0.00 sec)
```
ここまででdjangoの準備は完了です。

#Django REST frameworkの構築

Djangoの準備ができたところで、次はDjango側にAPIを作っていきます。
GETで上記テーブルの中身を取得するだけの簡易なものを作成します。
まずはsetting.pyに追記していきます。

ここはこちらを参考にしています。
[Django REST frameworkでAPIを作ってみた](https://qiita.com/ryo_mt09sp/items/1b36c414af3001d548e7)
[Django REST Framework で API サーバーを実装して得た知見まとめ(OAuthもあるよ）](https://qiita.com/furuuchin/items/c6d6230aa327ad7b337a)

```python:./django/composeexample/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', #追加
]
# ~略~
```

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

```
.
└─django
   └─myapp
       ├─migrations
       ├─admin.py
       ├─apps.py
       ├─model.py
       ├─tests.py
       ├─view.py
       ├─renderers.py #これ
       ├─serializers.py #これ
       ├─urls.py #これ
       └─__init__.py
```


```python:./django/myapp/serializers.py
from rest_framework import serializers
from .models import Person_data

class PersonSerializer(serializers.ModelSerializer):

    class Meta:
        model = Person_data
        fields = '__all__' 
```

```python:./django/myapp/renderers.py
import json
from rest_framework.renderers import JSONRenderer

class PersonJSONRenderer(JSONRenderer):
    charset = 'utf-8'  

    def render(self, data, accepted_media_type=None, renderer_context=None):
        return json.dumps({'person_data': data},ensure_ascii=False)
```

```python:./django/myapp/urls.py
from django.urls import path
from .views import PersonListApiView


urlpatterns = [
    path('get_person/', PersonListApiView.as_view()),
]
```
composeexample配下のurls.pyを編集し、myapp配下のurls.pyを有効にします。

```python:./django/composeexample/urls.py
"""composeexample URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include # includeを追加

from myapp import urls # myapp配下のurls.pyをimport


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


view.pyを編集します。

```python:./django/myapp/views.py
from django.shortcuts import render
from django.views.generic import ListView

from myapp.models import Person_data

from rest_framework import status
from rest_framework.generics import ListAPIView
from rest_framework.permissions import AllowAny

from .renderers import PersonJSONRenderer
from .serializers import PersonSerializer

# Create your views here.

class PersonListApiView(ListAPIView):
    model = Person_data # モデルを指定
    queryset = Person_data.objects.all()
    permission_classes = (AllowAny, )
    renderer_classes = (PersonJSONRenderer, ) # Rendererを指定
    serializer_class = PersonSerializer # Serializerを指定

```

settings.pyに下記を追記します。

```python:./django/composeexample/settings.py
REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    ]
}
```

http://localhost:8000/api/get_person/ を叩いてみます。
結果をjsonで取得てきていれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386475/3f3effac-74e9-6695-4a70-c2f25d2c0527.png)


#Nuxt.jsからAPIの結果を取得する

django側のAPIができたので、フロント側を作成していきます。
こちら参考にしてます。
[Nuxt.js・Vue.js の基本的な使い方を理解する](https://qiita.com/shun91/items/eefe0009462b75301dd1)
[Nuxt.js公式サイト](https://ja.nuxtjs.org/guide/installation/#-code-create-nuxt-app-code-%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B)

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

yarn create nuxt-app frontを入力後の選択肢は基本全てデフォルト値を入れていますが、
rendering modeだけSingle Page Appを指定しました。
ここでの設定はあとでnuxt.config.jsというファイルから変更可能です。

```bash
> docker exec -it nuxt /bin/bash

> yarn create nuxt-app front

? Project name front
? Project description My doozie Nuxt.js project
? Author name
? Choose the package manager Yarn
? Choose UI framework None
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose test framework None
? Choose rendering mode Single Page App
? Choose development tools (Press <space> to select, <a> to toggle all, <i> to invert selection)

```

下記は表示されれば完了です。

```bash
🎉  Successfully created project front

  To get started:

        cd front
        yarn dev

  To build & start for production:

        cd front
        yarn build
        yarn start

Done in 167.32s.
```

確認のために開発用サーバを動かす前に、nuxt.config.jsに追記します。
docker上でnuxt.jsを動かす際は下記が必要です。
[こちら参考](https://qiita.com/furu8ma/items/bc92bda79a1887a1485d)

```javascript:./nuxt/front/nuxt.config.js
export default {
  mode: 'spa',

  //ここから
  server: {
    port: 3000,
    host: '0.0.0.0',  
  },
  //ここまでを追記

  /*
  ** Headers of the page
  */
```


開発用サーバを動かしてみます。
完了後のメッセージにも記載されていますが、コンテナ内で作成したプロジェクトのフォルダに移動して、
yarn devです。

```bash
> cd front
> yarn dev
```

http://localhost:3000/ にアクセスしてみます。
下記表示であれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386475/322c2a10-8111-68cb-5698-7e84ddc455d1.png)

ここから編集していく際にホットリロードを有効にします。
docker上で開発するためには明示的に指定が必要です。
[こちら参考](http://kurowasi2525.hatenablog.com/entry/2018/04/29/201924)
下記をファイル末尾に追記します。

```javascript:./nuxt/front/nuxt.config.js
export default {
//~略~
  }, //追記の際はこのカンマを忘れずに
  watchers: {
    webpack: {
      poll: true
    }
  }
}
```

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

```javascript:./nuxt/front/nuxt.config.js
export default {
//~略~
  modules: [
    '@nuxtjs/axios',
  ],
  axios: {
    baseURL: "http://localhost:8000"
  },
//~略~
```

次に実際のページの編集をしていきます。
今回は簡便にするために、indexを編集していきます。

```vue:./nuxt/front/pages/index.vue
<template>
  <div class="container">
    <div>
      <logo />
      <h1 class="title">
        front
      </h1>
      <h2 class="subtitle">
        My doozie Nuxt.js project
      </h2>
      <div class="links">
        <a
          href="https://nuxtjs.org/"
          target="_blank"
          class="button--green"
        >
          Documentation
        </a>
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          class="button--grey"
        >
          GitHub
        </a>

<!-- ここから追記 -->
        <div v-for="d in dat.person_data" :key=d.person_name align="center">
          <h2>
            {{d.person_name}} {{d.person_age}} 
          </h2>
        </div>
<!-- ここまで追記 -->

      </div>
    </div>
  </div>
</template>

<script>
import Logo from '~/components/Logo.vue'

export default {
  components: {
    Logo
  },

//ここから追記
  data() {
    return {
      dat: []
    }
  },
  async mounted(){
    const url = "/api/get_person/" 
    const response = await this.$axios.get(url)
    this.dat = response.data
  }
//ここまで追記
}
</script>
//~略~
```

このままではCORSにひっかかるので、django側の設定をいじります。
setting.pyに追記します。

```python:./django/composeexample/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
```

下記のようにAPI経由で先ほどテーブルに入れたデータが取得できています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/386475/de5f25b7-e301-0c2b-9238-57c9e2e21e1a.png)

#おわりに

以上でAPI経由でのdjangoとNuxt.jsの簡単な連携ができました。
上記は開発環境のものなので、本番用にまたチューニングする必要があるかと思いますが、
勉強のため一度この段階で投稿させていただきました。