Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
126
Help us understand the problem. What is going on with this article?
@at-946

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

More than 1 year has passed since last update.

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 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"]
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

126
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
at-946
ゆっくりと。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
126
Help us understand the problem. What is going on with this article?