Nuxt.js + Rails(API) on DockerのHello Worldするべ!


Goal

友人からNuxt.jsいいよーと言われたのでひとまず触ってみるかというノリでNuxt.js + Rails(API) on DockerのHello Worldをめざします!

この記事のゴールはDocker上でフロントエンドとしてNuxt.js、バックエンドとしてRailsが連携しあってRailsのscaffold的にUserのCRUDができることです。

図にすると以下のような感じです。

Untitled Diagram (2).png


Table of contents


  1. Dockerコンテナの準備

  2. Nuxt.jsのHello world

  3. Rails(API)のHello world

  4. Nuxt.js + Rails(API)のHello world

Dockerやdocker-composeはすでにインストール済みの前提でいきます!


1. Dockerコンテナの準備

まずはNuxt.jsやRailsをDocker上で動作させるためのファイルの準備をしていきます。

Nuxt.jsは「docker で nuxt.js を開発環境を建てるだけ - Qiita」を参考にさせていただきました。

Rails(API)については「Rails on Docker(alpine)でAPIコンテナをつくってみた - Qiita」にて記事投稿しております。

詳細については各記事をご参考いただければ幸いですが、最終的なアウトプットは以下のようなものです。


DirectoryStructure

/

|--front/
| |--Dockerfile
|--back/
| |--Dockerfile
| |--Gemfile
| |--Gemfile.lock #空ファイル
|--docker-compose.yml


front/Dockerfile

FROM node:12.5.0-alpine

ENV HOME="/app" \
LANG=C.UTF-8 \
TZ=Asia/Tokyo

WORKDIR ${HOME}

RUN apk update && \
apk upgrade && \
npm install -g npm && \
npm install -g vue-cli

ENV HOST 0.0.0.0
EXPOSE 3000



back/Dockerfile

FROM ruby:2.6.3-alpine3.10

ENV RUNTIME_PACKAGES="linux-headers libxml2-dev make gcc libc-dev nodejs tzdata postgresql-dev postgresql" \
DEV_PACKAGES="build-base curl-dev" \
HOME="/app" \
LANG=C.UTF-8 \
TZ=Asia/Tokyo

WORKDIR ${HOME}

ADD Gemfile ${HOME}/Gemfile
ADD Gemfile.lock ${HOME}/Gemfile.lock

RUN apk update && \
apk upgrade && \
apk add --update --no-cache ${RUNTIME_PACKAGES} && \
apk add --update --virtual build-dependencies --no-cache ${DEV_PACKAGES} && \
bundle install -j4 && \
apk del build-dependencies

ADD . ${HOME}

CMD ["rails", "server", "-b", "0.0.0.0"]



back/Gemfile

source 'https://rubygems.org'

gem 'rails', '~>5'


docker-compose.yml

version: "3"

services:
db:
container_name: sample_db
image: postgres:11.4-alpine
environment:
- TZ=Asia/Tokyo
volumes:
- ./back/tmp/db:/var/lib/postgresql/data

back:
container_name: sample_back
build: back/
volumes:
- ./back:/app
depends_on:
- db
ports:
- 3000:3000

front:
container_name: sample_front
build: front/
command: npm run dev
volumes:
- ./front:/app
ports:
- 8080:3000


ここまでできたらbuildしてimageを作成しましょう。

$ docker-compose build


2. Nuxt.jsのHello world

imageができあがったらまずはNuxt.jsのアプリを作っていきます。

$ docker-compose run --rm front npx create-nuxt-app

? Project name --> sample_app # アプリ名
? Project description --> sample_app # アプリの説明
? Author name --> me # アプリの作成者
? Choose the package manager --> Npm
? Choose UI framework --> None
? Choose custom server framework --> None
? Choose Nuxt.js modules --> Axios
? Choose linting tools --> -
? Choose test framework --> None
? Choose rendering mode --> Universal (SSR)

Nuxtアプリが作成できたらアクセスできるか確認しときます。

$ docker-compose up front

http://localhost:8080にアクセスして以下のようなページにアクセスできればNuxt.jsのHello world完了です!

image.png

(参考)Nuxt.jsではホットリローディングというファイルの変更を自動で反映してくれる機能を有効にすることができます。npm run devコマンドでホットリローディングが有効になると公式で説明されていますが何やらうまくいくときといかない時がありました...うまくいかない場合は「IT研修でVuePress+Express+Nuxt on Dockerでシステムを作成した話 - エンジニアの卵の成長日記」を参考に以下のような設定を書き加えることでホットリロードされるようになりました。


front/nuxt.config.js

export default {

// 省略
watchers: {
webpack: {
poll: true
}
}
// 省略
}


3. Rails(API)のHello world

まずRailsアプリを作成しましょう。--apiをオプションにつけることでAPIモードに不要なもの、例えばViewなどが含まれないようにrails newすることができます。

$ docker-compose run --rm back rails new . -f -d postgresql --api

Railsアプリが作成されたらDB接続の設定をします。


back/config/database.yml

##### 省略

default: &default
adapter: postgresql
encoding: unicode
host: db # add
username: postgres # add
password: # add
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
##### 省略

DBの設定が完了したらDBを作成します。

$ docker-compose build back

$ docker-compose run --rm back rails db:create

DBの作成が完了したら、scaffoldでAPIを作ってみます。name属性をもつUserモデルを作ります。

$ docker-compose run --rm back rails g scaffold user name:string

$ docker-compose run --rm back rails db:migrate

Hello worldとして、testという名前のユーザーをAPIリクエストで作成してみます。

Rails APIでどのエンドポイントに何メソッドでリクエストすればいいかはrails routesコマンドで調べるとわかりやすいです。

$ docker-compose run --rm back rails routes

Prefix Verb URI Pattern Controller#Action
users GET /users(.:format) users#index
POST /users(.:format) users#create
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy

詳しい説明は省きますが、HTTPメソッド、URLパターン、アクションの関係性がわかるので、

ユーザーを作成する場合は「/usersにPOSTリクエスト」、特定のIDのユーザーの情報を取得する場合は「/users/:idにGETリクエスト」ということがわかります。

これを踏まえてcurlリクエストしてユーザー作成をしてきます。

$ docker-compose up -d back

$ curl -X POST http://localhost:3000/users -d 'user[name]=test'
$ curl http://localhost:3000/users/1
{"id":1,"name":"test","created_at":"2019-07-04T14:40:49.443Z","updated_at":"2019-07-04T14:40:49.443Z"}

これでRails APIのHello world完了です!

コンテナは停止しておきましょう。

$ docker-compose down


4. Nuxt.js + Rails(API)のHello world

さて、実際にはNuxt.jsのコンテナからRails(API)のコンテナにリクエストを流すので、Rails(API)のポートを外部に公開する必要はありません。誰からでもAPIのリクエストを受け取ってしまう状態はセキュリティ的にもよろしくありませんのでbackコンテナの外部公開ポートを削除しておきます。


docker-compose.yml

##### 省略

back:
container_name: sample_back
build: back/
volumes:
- ./back:/app
depends_on:
- db
# ports: # delete
# - 3000:3000 # delete
##### 省略

続いてNuxt.jsをいじっていきます。Hello worldでやりたいことは、



  1. http://localhost:8080/users/:idにアクセスして「Hello, (User.name)」と表示させたい


  2. http://localhost:8080/users/newにアクセスしてユーザーを追加したい

といったところにします。


4-1. http://localhost:8080/users/:idにアクセスして「Hello, (User.name)」と表示させたい

まずはhttp://localhost:8080/users/:idにアクセスした時にルーティングされるページを作成します。

$ mkdir front/pages/users

$ touch front/pages/users/_id.vue


front/pages/users/_id.vue

<template>

<h1>Hello, {{ name }}</h1>
</template>

<script>
export default {
asyncData({ $axios, params }) {
return $axios.$get(`http://back:3000/users/${params.id}`)
.then((res) => {
return { name: res.name }
})
}
}
</script>


$axios.$getでGETメソッドでAPIをリクエストしています。リクエスト先はhttp://back:3000/users/${params.id}としていますが、backはbackコンテナを意味していますので、Railsアプリが入ったコンテナの/user/${params.id}にGETリクエストを飛ばしていることになります。

レスポンスの値からnameを変数として取り出し、template内の{{ name }}に入れます。

この状態でコンテナを起動してhttp://localhost:8080/users/1にアクセスすると、Rails(API)のHello worldで作成したtestユーザーの情報が取得できています。

が、Backコンテナが立ち上がっていないといけない状態になったのでdepends_onしておきましょう。


docker-compose.yml

##### 省略

front:
container_name: sample_front
build: front/
command: npm run dev
volumes:
- ./front:/app
ports:
- 8080:3000
depends_on:
- back
##### 省略

$ docker-compose up

image.png


4-2. http://localhost:8080/users/newにアクセスしてユーザーを追加したい

続きましてユーザーの新規登録です。こちらはPOSTリクエストしてあげることで実現できます。

先ほどと同様にhttp://localhost:8080/users/newにアクセスしたときに表示されるページを作っていきます。

このページでは、Nameを入力してsubmitするとRails APIの方にPOSTリクエストを飛ばしてUserを新規登録できるようにしたいと思います。新規登録したらそのUserのHelloページ(4-1で作成)にページ遷移するようにしましょー。

まずはじめに、Rails APIにリクエストを飛ばせるようにconfigをいじっていきます。


front/plugins/axios.js

export default function({ $axios, redirect }) {

$axios.setToken('access_token')

$axios.onResponse(config => {
$axios.setHeader('Access-Control-Allow-Origin', 'http://back:3000')
})
}



front/nuxt.config.js

export default {

// 省略
plugins: [
'plugins/axios'
],
modules: [
'@nuxtjs/axios'
],
axios: {
proxy: true
},
proxy: {
'/api/': { target: 'http://back:3000', pathRewrite: { '^/api/': '/' } }
},
// 省略
}

ここらへんの設定をしないとCORSエラーってのがおきちゃう。すごくつまった。

Nuxt.jsのメソッド内で外部APIを叩くとcorsエラーが起きる - Qiita」「nuxt.js で axios から外部APIを叩くとCORSエラーを解決 - Qiita」の記事を参考にしました!

4-1で実施したasyncDataはSSRなので不要なようですが、通常メソッド内でリクエストをしたい場合は信頼するドメインへのリクエストのみを許可する必要があるみたいですね。

さて、上記の設定が終わったら実際にページを作っていきます。


front/pages/users/new.vue

<template>

<section>
<div>
<h1>New user</h1>
<form @submit.prevent="post">
<label for="name">Name: </label>
<input id="name" v-model="name" type="text" name="name" />
<button type="submit">submit</button>
</form>
</div>
</section>
</template>

<script>
export default {
data() {
return {
name: ''
}
},
methods: {
post() {
this.$axios.post(
'/api/users',
{
name: this.name
}
).then((res) => {
this.$router.push(`${res.data.id}`)
})
}
}
}
</script>


このページ(http://localhost:8080/users/new)にアクセスすると下のような画面が出てきます。

image.png

POSTリクエストするあたりを説明します!

5行目:<form @submit.prevent="post">

formをレンダリングしてますが、@submit.preventでsubmit時に22行目で定義しているpost()メソッドを呼び出してます。

22行目〜31行目:POSTリクエスト

大まかに形としては

this.$axios.post(url, data).then((res) => {成功した後の動作})

という感じです。

urlには先ほどproxyで定義した/api/を用いて/api/usersを指定します。これでhttp://back:3000/usersにリクエストすることになります。

dataにはリクエストデータを記載します。今回はform内で入力しているv-model="name"の値をリクエストしたいので、{ name: this.name }としてます。

成功した後の動作としては登録したUserのHelloページへ遷移するとしてました。ページ遷移はメソッド内の場合はthis.$router.push(パス)でできるので、レスポンスデータから登録されたidを取得して、

this.$router.push(`${res.data.id}`)

とすることでPOSTリクエストに成功したときに作成したユーザーのHelloページへ自動遷移されます。

ここまで作成したら、Helloページ側のGETリクエスト先も/api/を使った記述に変更しておきます。これしないとthis.$router.push時にリクエスト失敗しちゃいました。


front/pages/users/_id.vue

// 省略

- return $axios.$get(`http://back:3000/users/${params.id}`)
+ return $axios.$get(`/api/users/${params.id}`)
// 省略

ここまでで完成です。実際にhttp://localhost:8080/newにアクセスしてみてNameを入力しsubmitしてみましょう。

$ docker-compose up

入力したNameの「Hello, xxxxx」の画面に遷移したらNuxt.js + Rails(API)のHello worldは成功です!


Afterword

Nuxt.js初体験、Vue.jsとかも触ったことがなかったので結構苦戦をしいられました...

特にaxiosのPOSTリクエストには...かなりの時間を...うぅ...

同じくHello worldに苦しむ方の助けになれば幸いです!

引き続き勉強していかねば〜。


Reference