LoginSignup
33

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-01-29

自己紹介

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

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

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

GMOペパボ株式会社 採用サイト
yuki(ほりゆう)@運動(@yuki82511988) / Twitter

なぜこのアプリを作ったのか。この記事を書いたのか。

背景として、自分はエンジニアになる際にプログラミングスクール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をルートディレクトリ直下に作成します。

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.deventrypoint.sh作成します。

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"]
entrypoint.sh
#!/bin/bash
set -e

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

exec "$@"

今、ディレクトリ構造はこのような感じになっています。treeコマンド便利ですね。

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を編集してください。

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を試しに実行しやすくなります。

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

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

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ディレクトリを作成し、その中に下記のファイルを作成してください。

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

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

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

✊ React側からGraphQLを用いて、APIにアクセスできるか確認

frontendディレクトリで実行してください。
GraphQLとクライアントをインストールします。

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

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

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

docker-compose build

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

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

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

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がなくてエラーが出ますので、同じディレクトリの中に下記のファイルを作成します。

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というエラーがコンソールに出ていたのですが、下記を参考に修正しました。

💭 CIの設定

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

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

👮‍♀️ RSpecとRubocopの設定

API側ではRspecとRubocopを導入します。backendディレクトリで作業しましょう。

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が実行できているか確認しましょう。モデルに下記のバリデーションを追加します。

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

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

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ディレクトリ直下に作成してください。

.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を選択すると下記のページが出てきます。

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

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

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タブを確認して、うまくいくとこのようになります。

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

🟣 Herokuにデプロイ

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

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

git init
heroku create {api_name}

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

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

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

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

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です。

🔼 Vercelへ。フロントエンドのデプロイとCD設定。

Vrecelの登録をしましょう。すぐできます。
デプロイの設定も簡単すぎて本当に驚きました。

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

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

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

Error $Failed to fetch;

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

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

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

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

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

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

🙏 参考 & お礼

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

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

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

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

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

  • 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
形になってよかったです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33