Goal
友人からNuxt.jsいいよーと言われたのでひとまず触ってみるかというノリでNuxt.js + Rails(API) on DockerのHello Worldをめざします!
この記事のゴールはDocker上でフロントエンドとしてNuxt.js、バックエンドとしてRailsが連携しあってRailsのscaffold的にUserのCRUDができることです。
図にすると以下のような感じです。
Table of contents
- Dockerコンテナの準備
- Nuxt.jsのHello world
- Rails(API)のHello world
- 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」にて記事投稿しております。
詳細については各記事をご参考いただければ幸いですが、最終的なアウトプットは以下のようなものです。
/
|--front/
| |--Dockerfile
|--back/
| |--Dockerfile
| |--Gemfile
| |--Gemfile.lock #空ファイル
|--docker-compose.yml
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
FROM ruby:2.6.3-alpine3.10
ENV RUNTIME_PACKAGES="linux-headers libxml2-dev make gcc libc-dev nodejs tzdata postgresql-dev postgresql git" \
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"]
source 'https://rubygems.org'
gem 'rails', '~>5'
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完了です!
(参考)Nuxt.jsではホットリローディングというファイルの変更を自動で反映してくれる機能を有効にすることができます。npm run dev
コマンドでホットリローディングが有効になると公式で説明されていますが何やらうまくいくときといかない時がありました...うまくいかない場合は「IT研修でVuePress+Express+Nuxt on Dockerでシステムを作成した話 - エンジニアの卵の成長日記」を参考に以下のような設定を書き加えることでホットリロードされるようになりました。
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接続の設定をします。
##### 省略
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コンテナの外部公開ポートを削除しておきます。
##### 省略
back:
container_name: sample_back
build: back/
volumes:
- ./back:/app
depends_on:
- db
# ports: # delete
# - 3000:3000 # delete
##### 省略
続いてNuxt.jsをいじっていきます。Hello worldでやりたいことは、
-
http://localhost:8080/users/:id
にアクセスして「Hello, (User.name)」と表示させたい -
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
<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
しておきましょう。
##### 省略
front:
container_name: sample_front
build: front/
command: npm run dev
volumes:
- ./front:/app
ports:
- 8080:3000
depends_on:
- back
##### 省略
$ docker-compose up
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をいじっていきます。
export default function({ $axios, redirect }) {
$axios.setToken('access_token')
$axios.onResponse(config => {
$axios.setHeader('Access-Control-Allow-Origin', 'http://back:3000')
})
}
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なので不要なようですが、通常メソッド内でリクエストをしたい場合は信頼するドメインへのリクエストのみを許可する必要があるみたいですね。
さて、上記の設定が終わったら実際にページを作っていきます。
<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
)にアクセスすると下のような画面が出てきます。
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
時にリクエスト失敗しちゃいました。
- 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
- Nuxt.js - ユニバーサル Vue.js アプリケーション
- docker で nuxt.js を開発環境を建てるだけ - Qiita
- Rails on Docker(alpine)でAPIコンテナをつくってみた - Qiita
- IT研修でVuePress+Express+Nuxt on Dockerでシステムを作成した話 - エンジニアの卵の成長日記
- Axios モジュールの使いかた | Nuxt.js における REST API の活用
- Nuxt.jsのメソッド内で外部APIを叩くとcorsエラーが起きる - Qiita
- nuxt.js で axios から外部APIを叩くとCORSエラーを解決 - Qiita
- Hot reloading doesn't work in docker container on Windows · Issue #2481 · nuxt/nuxt.js
- プログラムによるナビゲーション | Vue Router