DockerでRailsAPIとReactとGraphQL、MySQLの環境を構築し、更にGithubActionsでCIを設定し、最後にHeroku(有料)+Vercelにデプロイするまでの流れ。

## 自己紹介
渋谷のGMOペパボという会社でエンジニアをしております。ほりゆうと申します。
Twitterではyukiという名前でも活動しています。

最初のキャリアは高等学校の国語科の教員で、今はエンジニアになって2年ちょっとが経ちました。
エンジニアとしては2社目です。よろしくお願いします。

少し宣伝です。GMOペパボではエンジニアを積極的に採用しており、研修付きの採用枠もあります。
福利厚生も充実していて、最近は無料のジムを使わせていただいております。
少しでも興味のある方はいつでもDMください！

[GMOペパボ株式会社 採用サイト](https://recruit.pepabo.com/)
[yuki\(ほりゆう\)@運動（@yuki82511988） / Twitter](https://twitter.com/yuki82511988)

### なぜこのアプリを作ったのか。この記事を書いたのか。
背景として、自分はエンジニアになる際にプログラミングスクールDMM WEBCAMPさんでの学習を経て転職を行いました。
転職活動の際、なかなかスクール以外のチーム開発経験を語ることが難しいなと感じました。

そこで、今自分が主体となってアプリケーションを開発し、転職活動をしている人にも参加していただくことで、何かアウトプットのサポートに寄与できたらなと思いました。

またエンジニア転職はしたけれど、あまり開発ができていない（でも開発したい）方と一緒に開発ができたらなと思い、この開発企画を進めています。

自身のスキルアップにもなりますし、一石二鳥です🙏

本記事では、そのアプリケーションの環境構築が結構大変だったので、誰かのためになればと思って記事にしました。

試行錯誤して作成したものが多く、至らぬ点など多いと思いますが、どうぞよろしくお願いします。
おそらく、必要のない処理やアンチパターンもあるかと思います。

疑問点・改善点などあればコメントにてご指摘いただけますと幸いです。

## 前置き
少し量が多いので、1つ1つ何をしているのかの説明は省いたり、サービスの登録やログインなどは端折って説明することをお許しください。
わかりにくいことがあれば、コメントで聞いていただいても大丈夫です。

## 📦 作業ディレクトリの作成
好きなアプリケーション名のディレクトリを作成してください。これ以降は、こちらをルートディレクトリと呼びます。
私はディレクトリ名を`diary`にしました。

その中に、`frontend`というディレクトリも作成お願いします。

## 🚃 Railsの雛形APIを作成する
以下の作業はルートディレクトリで行います。

では早速、まずはローカルでAPIの雛形を作成します。

`rails new backend -d mysql --api`

作成されたプロジェクトから`.git`は削除しておきます。
`--skip-git`オプションをつけてもいいのですが、`.gitignore`を生成して欲しいのでこのようにしました。

## 🔍 Reactの雛形アプリを作成する
次に、フロントエンド側のアプリケーションを作りましょう。`frontend`ディレクトリの中で下記を実行します。

`yarn create react-app app --template typescript`

作成されたプロジェクトから、こちらも`.git`を削除します。


## 🐳 Dockerの環境作成を行う
次に、開発環境の肝となるDockerを整備していきます。

今回は`docker-compose.yml`をルートディレクトリに置き、`backend`ディレクトリにのみ`Dockerfile`を置きます。
今の段階では、特にフロントエンド側で独自の設定をしないのでこのような構成にしています。

`docker-compose.yml`をルートディレクトリ直下に作成します。

```yml:docker-compose.yml
version: "3"

services:
  db:
    platform: linux/x86_64
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    command: --default-authentication-plugin=mysql_native_password
    ports:
      - 3306:3306

  api:
    build:
      context: ./backend/
      dockerfile: Dockerfile.dev
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    image: rails:dev
    volumes:
      - ./backend:/sample
    environment:
      TZ: Asia/Tokyo
      RAILS_ENV: development
    ports:
      - 3001:3000
    depends_on:
      - db

  front:
    image: node:19.3.0-alpine3.17
    volumes:
      - ./frontend/app:/usr/src/app
    working_dir: /usr/src/app
    command: sh -c "yarn && yarn start"
    ports:
      - "4000:3000"

volumes:
  mysql-data:
```

また、`backend`の直下に`Dockerdfile.dev`と`entrypoint.sh`作成します。

```dockerfile:Dockerfile.dev
FROM ruby:3.2
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

ENV APP_PATH /sample

RUN mkdir $APP_PATH
WORKDIR $APP_PATH

COPY Gemfile $APP_PATH/Gemfile
COPY Gemfile.lock $APP_PATH/Gemfile.lock
RUN bundle install

COPY . $APP_PATH

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

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

```sh:entrypoint.sh
#!/bin/bash
set -e

rm -f /sample/tmp/pids/server.pid

exec "$@"
```

今、ディレクトリ構造はこのような感じになっています。treeコマンド便利ですね。
```console
diary
├── backend
├── docker-compose.yml
└── frontend
```

ビルドします。ルートディレクトリで実行してください。

`docker-compose build`

`docker-compose up`

自分はフロントエンドの起動に時間がかかりました。

問題なく起動できた場合、下記でそれぞれのページにアクセスできます。

Rails: http://localhost:3001/
React: http://localhost:3001/


## 🐬 DBの設定
では、DBの設定をします。
`backend`ディレクトリで実行してください。

API側の下記のファイルを編集してください。

```database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # ここを変更しました
  host: db # ここを変更しました
```

DBを作成していきます。

`docker-compose run api rails db:create`

初期データとして、Diaryの記事であるJournalテーブルを作っていきます。

`docker-compose run api rails d model journal title:text content:text`
`docker-compose run api rails db:migrate`

これでローカルのDBが作成できました。

## 📈 GraphQLの導入
次に、GraphQLを導入していきましょう。APIもフロントエンドも関わるので少し大変です。
まずはGemfileを編集してください。

```ruby:Gemfile
group :development do
  # Speed up commands on slow machines / big apps [https://github.com/rails/spring]
  # gem "spring"
  gem 'sass-rails' # ここを追加
  gem 'graphiql-rails' # ここを追加
end

gem 'graphql'  # ここを追加
```

`docker-compose build`

Gemのインストールができたら、GraphQLの設定をします。
`docker-compose run api bundle exec rails g graphql:install                          `

ルーティングに下記の記述を追加してください。開発環境でGraphQLを試しに実行しやすくなります。

```ruby:Route.rb
  if Rails.env.development?
    mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "graphql#execute"
  end
```


config配下の`application.rb`にも設定を加えます。

```ruby:application.rb
require_relative "boot"
require "rails/all"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Backend
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 7.0

    # Configuration for the application, engines, and railties goes here.
    #
    # These settings can be overridden in specific environments using the files
    # in config/environments, which are processed later.
    #
    # config.time_zone = "Central Time (US & Canada)"
    # config.eager_load_paths << Rails.root.join("extras")

    # Only loads a smaller set of middleware suitable for API only apps.
    # Middleware like session, flash, cookies can be added back manually.
    # Skip views, helpers and assets when generating a new resource.
    config.middleware.use ActionDispatch::Session::CookieStore # ここを追加

    config.api_only = true
  end
end
```

最後に`backend/app/assets/config`ディレクトリを作成し、その中に下記のファイルを作成してください。

```js:manifest.js
//= link graphiql/rails/application.css
//= link graphiql/rails/application.js
```

http://localhost:3001/graphiql にアクセスしてください。

下記の画面が表示され、テスト用のクエリを実行してHello World!が帰ってきたら優勝です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557847/d54795b8-7596-e5b7-b32c-425451bff1e4.png" width="720px">



## ✊ React側からGraphQLを用いて、APIにアクセスできるか確認
`frontend`ディレクトリで実行してください。
GraphQLとクライアントをインストールします。

`docker-compose run front yarn install @apollo/client graphql`

次に、API側でも少し修正を加えます。

```ruby:Gemfile
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem "rack-cors" # ここのコメントアウトを外す
```

`docker-compose build`

corsの設定をする下記のファイルを、以下のように変更してください。
`origins "*"`の箇所は良くないので、今回は一旦学習用として後で修正しましょう。

```ruby:cors.rb
# Be sure to restart your server when you modify this file.

# Avoid CORS issues when API is called from the frontend app.
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.

# Read more: https://github.com/cyu/rack-cors

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "*"

    resource "*",
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
```

フロントエンドの下記のファイルを修正して、試しにクエリが実行されるようにします。

```tsx:App.tsx
import './App.css';
import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider
} from "@apollo/client";
import { Sample } from './Sample';


const client = new ApolloClient({
  uri: 'http://localhost:3001/graphql',
  cache: new InMemoryCache()
});

function App() {
  return (
    <ApolloProvider client={client} >
      <Sample />
    </ApolloProvider>
  );
}

export default App;
```

上記のコードを書いただけだと`Sample`がなくてエラーが出ますので、同じディレクトリの中に下記のファイルを作成します。

```tsx:Sample.tsx
import React from "react";
import { useQuery, gql } from "@apollo/client";

const testField = gql`
  query GetTestField {
    testField
  }
`;

export const Sample = () => {
  const { loading, error, data } = useQuery(testField);
  
  if (loading) return <>'ロード中....'</>;
  if (error) return <>`Error ${error.message}`;</>;

  return (
    <>
      {data.testField}
    </>
  );
};
```

http://localhost:4000/ にアクセスして、ロード中の表示ののちにHello World!が表示されたら成功です。

### 備考
自分の環境では`ws://localhost:3000/ws`というエラーがコンソールに出ていたのですが、下記を参考に修正しました。

https://github.com/facebook/create-react-app/issues/11779#issuecomment-1039783376


## 💭 CIの設定
ここまでで、ローカルでの環境構築は完了です。CIやCDを設定して、より便利にしていきましょう。
下記のコマンドを`frontend`ディレクトリで実行してください。

` docker-compose run front yarn add prettier --dev --exact`

### 👮‍♀️ RSpecとRubocopの設定
API側ではRspecとRubocopを導入します。`backend`ディレクトリで作業しましょう。

```ruby:Gemfile
group :development do
  # Speed up commands on slow machines / big apps [https://github.com/rails/spring]
  # gem "spring"
  gem 'rspec-rails', '~> 4.0.1', '>= 4.0.1' # ここを追加
  gem 'rubocop', require: false # ここを追加
  gem 'graphiql-rails'
  gem 'sass-rails'
end
```

`docker-compose run api rails g rspec:install`

`docker-compose build`

まずは試しに、RSpecが実行できているか確認しましょう。モデルに下記のバリデーションを追加します。

```ruby:journal.rb
class Journal < ApplicationRecord
  validates :title, length: { minimum: 2 }
end
```

`docker-compose run api rails g rspec:model journal`
テストの雛形を作成して...テストも作成します。

```ruby:journal_spec.rb
require 'rails_helper'
require 'spec_helper'

describe Journal do
  it '1文字のタイトルは保存されない' do
    journal = Journal.new(title: 'h')
    expect(journal).to be_invalid

    journal.title = 'hoge'
    expect(journal).to be_valid
  end
end
```

`docker-compose run api bundle exec rspec`

```
Creating diary_api_run ... done
.

Finished in 0.19548 seconds (files took 10.42 seconds to load)
1 example, 0 failures
```

エラーが出ずにPASSすればOKです。

次にRubocopoの設定ファイルを書いていきましょう。`backend`ディレクトリ直下に作成してください。

```yml:.rubocop.yml
AllCops:
  NewCops: enable
  SuggestExtensions: false
  Exclude:
    - "vendor/**/*"
    - "db/**/*"
    - "config/**/*"
    - "bin/*"
    - "node_modules/**/*"
    - "Gemfile"
Style/Documentation:
  Enabled: false
Style/FrozenStringLiteralComment:
  Enabled: false
MethodLength:
  CountComments: true
  Max: 24
Naming/MethodParameterName:
  Enabled: false
```

`docker-compose run api bundle exec rubocop -A` で綺麗になります。

### 🐙 Github Actionsの設定

では、Github側の設定をしていきましょう。リポジトリを作成してください。

リポジトリのSettings、左タブのCode and automationのActions > Generalsを選択すると下記のページが出てきます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557847/2f9b70b8-007f-605b-0b40-e4c5e4588734.png" width="720px">


フロントエンドではコードのフォーマットをかけた後、自動でコミットを作成してくれるようなCIを作成するので、ここでwriteの権限を付与します。

ではローカルに戻ります。ルートディレクトリ直下に、`.github`ディレクトリを作成します。その中に、Github Actionsの設定ファイルを作成します。

```yml: main.yml
name: CI

on: push

env:
  FILE_PATTERN: "./frontend/app/src/**/*.{ts,tsx,js,jsx}"

jobs:
  front:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 19.x
      - name: Run lint
        run: |
          cd frontend/app
          yarn install
          yarn eslint --fix --quiet --ext .jsx,.js,.tsx,.ts './src/**/*.{ts,tsx,js,jsx}'

      - name: Run Prettier
        run: |
          npx prettier --write ${FILE_PATTERN}
      - uses: stefanzweifel/git-auto-commit-action@v3.0.0
        with:
          commit_message: Apply CI Prettier Change


  api:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup Docker
        shell: bash
        run: |
          docker-compose build
          docker-compose up -d
          sleep 30
          docker-compose run api rails db:create db:migrate
        env:
          RAILS_ENV: test

      - name: Run Rubocop
        shell: bash
        run: |
          docker-compose run api bundle exec rubocop
        env:
          RAILS_ENV: test

      - name: Run RSpec
        shell: bash
        run: |
          docker-compose run api bundle exec rspec
        env:
          RAILS_ENV: test
```

ローカルのルートディレクトリで`git init`を実行して必要なファイル全てをコミットして、リモートリポジトリにプッシュしましょう。

Actionsタブを確認して、うまくいくとこのようになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557847/1cfbaded-8f7c-89e6-6a72-5abe77b39c5e.png" width="720px">


`git pull`を実行すると、Github Actionsで修正されたフロントエンドのコミットがローカルに反映されるはずです。
お疲れ様でした。

## 🟣 Herokuにデプロイ
ここからは有料のサービスを使用します。よく調査の上、自己責任で進めてください。
Herokuの登録、CLIのインストール、クレジットカードの登録は済んでいる前提です。

`backend`ディレクトリで作業をします。

`git init`
`heroku create {api_name}`   

api_nameには好きなアプリ名を入れましょう。{}は不要です。

本番用のDockerfileを`backend`直下に作成します。

```Dockerfile:Dockerfile
FROM ruby:3.2
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

COPY Gemfile* ./
RUN bundle install
COPY . .

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

EXPOSE 3000

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

Rubyの3.2.0で環境を構築しているので、ビルドパックの設定をします。
`heroku buildpacks:set https://github.com/heroku/heroku-buildpack-ruby.git\#v250 --remote origin`

自分はM1のMacを使用していたのでこちらも実行しました。
`bundle lock --add-platform x86_64-linux`

普通は`heroku.yml`を作成してぱぱっとデプロイできるようですが、どん詰まりしたので下記の方法で直接リリースします。

```
docker buildx build . --platform linux/amd64 -t username/{api_name}:latest
docker tag username/{api_name} registry.heroku.com/{api_name}/web
docker push registry.heroku.com/{api_name}/web
heroku container:release web -a {api_name}
```

`heroku logs --tail -a {api_name}`を実行してログを確認してエラーがなければOKです。

ルーティングを設定していないので、アプリにアクセスしても404が出ていますが、他のエラー出てなければ大丈夫です。

※どん詰まりした原因がわかり、heroku.ymlでデプロイできたので記事にまとめました。よろしければご覧ください。

**【Rails/Heroku】「`write': No such file or directory @ rb_sysopen - tmp/pids/server.」または、用意したのにYour app does not include a heroku.yml に陥った時 - Qiita**
https://qiita.com/yuki82511988/items/7ef9a0e39f76d068deb8


## 🟪 HeokuにcreaDBのアドオン入れる
今回はMySQLをサポートしているclearDBというアドオンを使用します。~~Postgresのアドオンを使用すると確かお金がかかるからです。~~

Herokuの管理画面からadd-onを選択し、clearDBを検索して導入してください。
うまくインストールできると、下記のコマンドで重要な情報が見られます。

`heroku config --app {api_name}`

```
CLEARDB_DATABASE_URL:
mysql://{user_name}:{password}@{host_name}/{database_name}?reconnect=true
```

※具体的な値が出力されるので、置き換えています。
では、この情報をそれぞれ環境変数にセットしていきましょう。

```console
heroku config:add DB_USERNAME='<user_name>'
heroku config:add DB_PASSWORD='password'
heroku config:add DB_HOSTNAME='host_name'
heroku config:add DB_NAME='database_name'
heroku config:add DB_PORT='3306'
heroku config:add DATABASE_URL='mysql2://{user_name}:{password}@{host_name}/{database_name}?reconnect=true'
```

DATABASE_URLのmysqlの後ろを「2」にするのを忘れないでください。

`heroku rake db:migrate`

エラーなければ完了です。優勝です。

## 💿 APIのCDの設定
では、mainブランチにマージされたらデプロイするというCDを実現させましょう。

非常に簡単です。
Herokuの管理画面の「Deploy」からDeployment methodでGithubを選択しましょう。
その後、下に続く「Automatic deploys」を「Enable」にすればOKです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557847/a2ea43a0-9bb4-6323-c5f6-686c8b829327.png" width="720px">


## 🔼 Vercelへ。フロントエンドのデプロイとCD設定。
Vrecelの登録をしましょう。すぐできます。
デプロイの設定も簡単すぎて本当に驚きました。

https://vercel.com/

~~ぽちぽち押していくといい感じに進みます。~~
少しご説明するとプロジェクトを作成し`Import Git Repository`で作成したリポジトリをインポートします。
Framework PresetはCreateReact appを選択します。



ルートディレクトリ（ここでは、プロジェクトのルートディレクトリではなくfrontendのアプリケーションの話をしています）は`/frontend/app`なことに注意してください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/557847/61157a5d-cb48-3c20-bad5-bb42a8657907.png" width="720px">


アプリケーションの画面にいくと...

`Error $Failed to fetch;`

ダメそうです。なぜでしょう。


それは、`App.tsx`のエンドポイントの指定が間違っているからですね。
下記のように修正します。

```tsx:App.tsx
const client = new ApolloClient({
  uri: "https://{api_name}.herokuapp.com/graphql",
  cache: new InMemoryCache(),
});
```

では、`git add *`をして、コミットプッシュまで行いましょう。
Vercelの方は何も設定しなくても、勝手にデプロイしてくれます。



→ Hello World! が表示されたらバッチリです！！！！！！！！！！！！

できた...お疲れ様でした。。。



## 🙏 参考 & お礼
皆様ありがとうございました。お世話になった記事を記載します。

GraphQLの導入の際に参考にさせていただきました。

https://zenn.dev/lilac/books/37bdf5d90b5f9b/viewer/ed9812

一番詰まりました。heroku.ymlだとなぜうまくいかないのでしょうか...🤔
まだわからないので、引き続き調査が必要です。

https://zenn.dev/daku10/articles/m1-heroku-container-trouble-exec-format-error


MySQLを使用されていたので、非常に参考になりました。

https://qiita.com/murakami-mm/items/9587e21fc0ed57c803d0

## 😞 詰まりポイントや雑談、今後の挑戦

- database.rbの設定
`Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)`
ちゃんとしないとこうなる。productionの設定はここに書くのか、Heroku側に書くのか理解するのに時間がかかりました。

- ApolloClientの使い方を調べるのに時間がかかりました

- Dockerfileに書くべきものなのか、docker-compose.ymlファイルに書くべきものなのか
他のエンジニアの方にもご相談させていただきました。

- CIでDatabaseに接続できないエラー
コンテナが起動していなかった。今は`sleep 30`をして対応。もっといい方法を探したいです。

- Rails7を使用したら...
`ActionDispatch::Request::Session::DisabledSessionError in GraphiQL::Rails::Editors#show`

などなど、本当に苦労しました...orz
形になってよかったです。