自己紹介
渋谷の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
をルートディレクトリ直下に作成します。
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
作成します。
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"]
#!/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側の下記のファイルを編集してください。
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を編集してください。
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を試しに実行しやすくなります。
if Rails.env.development?
mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "graphql#execute"
end
config配下の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
ディレクトリを作成し、その中に下記のファイルを作成してください。
//= 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側でも少し修正を加えます。
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem "rack-cors" # ここのコメントアウトを外す
docker-compose build
corsの設定をする下記のファイルを、以下のように変更してください。
origins "*"
の箇所は良くないので、今回は一旦学習用として後で修正しましょう。
# 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
フロントエンドの下記のファイルを修正して、試しにクエリが実行されるようにします。
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
がなくてエラーが出ますので、同じディレクトリの中に下記のファイルを作成します。
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
ディレクトリで作業しましょう。
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が実行できているか確認しましょう。モデルに下記のバリデーションを追加します。
class Journal < ApplicationRecord
validates :title, length: { minimum: 2 }
end
docker-compose run api rails g rspec:model journal
テストの雛形を作成して...テストも作成します。
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
ディレクトリ直下に作成してください。
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の設定ファイルを作成します。
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
直下に作成します。
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
のエンドポイントの指定が間違っているからですね。
下記のように修正します。
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
形になってよかったです。