Ruby on Rails + Vue.js + AWS Fargate + Circle CiでSPAアプリを作成してみた


## はじめに
最近、と言ってもこのアプリを作ったのは2021年終わりから2022年前半くらいになるのですが、
未経験エンジニアのポートフォリオのレベルがだいぶ上がっているという話を耳にすることが多く、未経験で業界に飛び込んだ自分も負けていられないなという思いから業務後にコツコツアプリを作ってみました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594063/008e58f3-edd0-28a4-272c-c131ea5d1a5d.png" width=80% >


先に言い訳をしておくと、転職活動中だったこともあり`Terraform`でインフラのコード化までやり切ることができず、テストコードも未実装というかなりお粗末なアプリになってしまいました。反省。。。
ただ、業務と並行して未経験の技術をキャッチアップしていたことが転職面接時には結構ウケが良かったので、ある程度プラス評価はしてもらったかなと思っています。
<b>あとシンプルに自分の作りたいものが形になっていくのはやっぱり楽しいですね！<b>

話を戻すと、当時は実務で使用したことのなかった技術が多かったこともありかなり手探りで実装しました。
<b>この記事では、そのときに参考にした学習教材やブログなどをまとめてみました。</b>
だいぶ色々なサイトを巡回したこともあり、おそらく記載漏れがあるので適宜更新したいと思います。

## アプリ概要
ファッションが好きなこともあり、セレクトショップや古着屋に行くことが多かったのですが、毎回インスタやGoogleマップで場所を調べるのにうんざりしていたので、こうしたショップをまとめた「食べログ」みたいなサイトがあれば便利かなと思って作ってみました。


懺悔するとフロント側は完全に某マッチングアプリの画面をパ○りました。 
メインはバックエンド側とインフラ側だったのでご勘弁を............
![165461717-f311b9f8-e88a-46c1-a71d-dd8512079326.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594063/76184cb3-4fa9-a99f-b8b9-a65c9f59be73.gif)
![165465805-c58f478d-3ed0-487c-98a9-d38fa384edc9.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594063/9f3e1a38-8e1b-d22c-12ca-cc1e78d05a82.gif)


## 開発環境 
・Vue.js 3.2.31 
・Ruby 2.6.6 
・Ruby on Rails 6.0.4.6
 ・MySQL 5.6
 ・Docker 20.10.7 
・Circle Ci 2.1

## ディレクトリ構成
<b>shop</b>
  ┣ <b>.circleci </b>
  ┣ <b>frontend</b> (Vueアプリケーションを配置)
  ┗ <b>shop_api</b> (Railsアプリケーションを配置)

## Dockerfile

```Dockerfile
# shop/frontend/Dockerfile

FROM node:12.18.3-alpine

ENV APP_HOME /app
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

RUN apk update && \
    npm install -g @vue/cli && \
    npm install --save axios
```


```Dockerfile
# shop/shop_api/Dockerfile.development

FROM ruby:2.6.6

RUN apt-get update -qq && \
    apt-get install -y build-essential \
    libpq-dev \
    nodejs \
    && rm -rf /var/lib/apt/lists/*

RUN mkdir /app
ENV APP_ROOT /app
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN gem install bundler:2.3.6 && \
    bundle install
```


```Dockerfile
# shop/shop_api/Dockerfile.production

FROM ruby:2.6.6

ENV RAILS_ENV=production

ENV LANG C.UTF-8
ENV TZ Asia/Tokyo

RUN mkdir /app
WORKDIR /app

ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock

RUN apt-get update -qq && \
    apt-get install -y build-essential \
    libpq-dev \
    sudo \
    nginx && \
    gem install bundler:2.0.1

RUN bundle install

ADD . /app
RUN mkdir -p tmp/sockets
RUN mkdir -p tmp/pids

# nginx
RUN groupadd nginx
RUN useradd -g nginx nginx
ADD nginx/nginx.conf /etc/nginx/nginx.conf

EXPOSE 80

RUN chmod +x /app/entrypoint.sh

CMD ["/app/entrypoint.sh"]
```

```Docker-compose.yml
## shop/docker-compose.yml

version: "3"

services:
  db:
    container_name: db
    image: mysql:5.7.30
    environment:
      TZ: Asia/Tokyo
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
      MYSQL_ROOT_PASSWORD: password
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - 3307:3306
    networks:
      app_net:
        ipv4_address: '172.20.0.2'

  frontend:
    container_name: frontend
    image: frontend
    build: ./frontend
    volumes:
      - ./frontend:/app
    ports:
      - '8080:8080'
    tty: true
    stdin_open: true
    command: npm run serve

  shop_api:
    container_name: shop_api_development
    build:
      context: ./shop_api/
      dockerfile: Dockerfile.development
    image: shop_api_development
    command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails server -b 0.0.0.0"
    tty: true
    stdin_open: true
    volumes:
      - ./shop_api:/app:cached
      - bundle_data:/usr/local/bundle:cached
      - /app/vendor
      - /app/tmp
      - /app/log
      - /app/.git
    environment:
      TZ: Asia/Tokyo
    depends_on:
      - db
    ports:
      - 3000:3000
    networks:
      app_net:
        ipv4_address: '172.20.0.3'

networks:
  app_net:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/24

volumes:
  mysql_data:
  bundle_data:
```

## CircleCi
```config
# shop/.circleci/config.yml

version: 2.1

jobs:
  shop_api:
    docker:
      - image: circleci/ruby:2.6.6-stretch-node
        environment:
          RAILS_ENV: test
          DB_HOST: 127.0.0.1
      - image: circleci/mysql:5.7.30
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
          MYSQL_ROOT_PASSWORD: password

    working_directory: ~/repo

    steps:
      - checkout

      # restore gem from cache
      - restore_cache:
          keys:
            - gem-cache-v1-{{ checksum "~/repo/shop_api/Gemfile.lock" }}
            - gem-cache-v1-
          working_directory: ~/repo/shop_api

      # gem install
      - run:
          command: |
            gem install bundler
            bundle config set path 'vendor/bundle'
            bundle install --jobs=4 --retry=3
          working_directory: ~/repo/shop_api

      - save_cache:
          key: gem-cache-v1-{{ checksum "~/repo/shop_api/Gemfile.lock" }}
          paths:
            - ~/repo/shop_api/vendor/bundle
          working_directory: ~/repo/shop_api

      # Database setup
      - run:
          command: bundle exec rails db:create
          working_directory: ~/repo/shop_api
      - run:
          command: bundle exec rails db:migrate
          working_directory: ~/repo/shop_api

      - run:
          name: create directory to store test results
          command: mkdir /tmp/test-results
          working_directory: ~/repo/shop_api

      # run tests
      - run:
          name: RSpec
          command: |
            bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) \
              || (printf "====== RETRYING...\n\n\n"; bundle exec rspec --only-failures)
          working_directory: ~/repo/shop_api

      # collect reports
      - store_test_results:
          path: /tmp/test-results
      - store_artifacts:
          path: /tmp/test-results
          destination: test-results
      - store_artifacts:
          path: /home/circleci/repo/tmp/screenshots

  frontend:
    docker:
      - image: circleci/node:14.3
        environment:
          NODE_ENV: test

    working_directory: ~/repo

    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "~/repo/frontend/package.json" }}
          working_directory: ~/repo/frontend
      - run:
          name: install-packages
          command: npm install
          working_directory: ~/repo/frontend
      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "~/repo/frontend/package.json" }}
          working_directory: ~/repo/frontend
      - run:
          name: test
          command: npm run test
          working_directory: ~/repo/frontend

workflows:
  version: 2
  test:
    jobs:
      - shop_api:
          filters:
            branches:
              ignore: main
      - frontend:
          filters:
            branches:
              ignore: main
```


## インフラ構成図
￼![167063081-2af6c327-a6d9-4d04-8347-fc294ffa63cd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594063/e2676d22-4272-1f43-cdd5-4c87daf76423.png)



## 参考にした教材、記事

### Ruby 
<b>『プロを目指す人のためのRuby入門』</b>
![51nY-YLt2ZL._SX395_BO1,204,203,200_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594063/68d3b6c3-f5ec-df88-b3d5-31c422b22a85.jpeg)

https://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%92%E7%9B%AE%E6%8C%87%E3%81%99%E4%BA%BA%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AERuby%E5%85%A5%E9%96%80-%E8%A8%80%E8%AA%9E%E4%BB%95%E6%A7%98%E3%81%8B%E3%82%89%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%E3%83%BB%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E6%8A%80%E6%B3%95%E3%81%BE%E3%81%A7-Software-Design-plus%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA/dp/4774193976


<b>Rubyを学ぶにはやはりこれ</b>
ボリュームはありますが、わかりやすい説明でスッと理解しやすい技術書でした。 Qiitaにも学習後のアウトプットを投稿している人も多くいるので、振り返りでサクッと読み返したりするのも効果的かなと思います。



#### RSpec
著者の伊藤さんはQiitaなどでRSpecやRubyやRailsでリファクタリングに使えるメソッドやコードの書き方などを体系的に投稿しているので、こちらもかなり勉強になる内容でした。

RSpecは`describe`や`context`の関係、マッチャなどの聞きなれない用語もそうですが、`let`の遅延評価など独特な構文を持っているため最初は？？？となることが多く、苦手意識を持ちやすいので、ここで基礎を固めてしまうのが良いと思います。

Qiitaは書籍と異なり体系的に技術を学ぶことは難しいですが、この記事では入門から応用までパート別で解説をしており、初学者でも学びやすい内容だと思います。
自分がRSpecを書くときに一番役に立ちました！

と言いつつポートフォリオではテストコードを書いてない矛盾…………………
その後、実務でキャッチアップしたので許してほしい……………..(まあまあ苦労した)

https://qiita.com/jnchito/items/dedb3b889ab226933ccf

https://qiita.com/jnchito/items/42193d066bd61c740612


#### Rails APIモード
Rails APIモードに関しては、こちらでサクッと勉強してみた覚えがあります。

https://www.techpit.jp/courses/195

`Postman`を使用して、APIのテストなんかもやっていました。
`Swagger`とか導入できたらWeb上でサクサクAPIいじれたのかなと思う今日この頃。

それかもっとモダンな技術があるかも.........


### Vue.js 
https://www.udemy.com/course/learn-vuejs/?utm_source=aff-campaign&LSNPUBID=fEh2HjeBHSU&utm_medium=udemyads&ranMID=47984&ranSiteID=fEh2HjeBHSU-aS0Ogw0C0x.kS4EgAayQmw


Vue.jsの基礎を学習するのに利用しました。
説明も丁寧でわかりやすく、基礎を固める上でかなり有効な教材でした。

Qiitaで学習するのも結構好きなんですが、やっぱり体系的に学習しとくと安心感?があります。

### AWS
#### VPC、サブネットなどの基礎
https://qiita.com/maru401/items/8e7d32a8baded045adb2

こちらの記事では割と初期のVPCやサブネットの作成などを参考にしました。
どうしても忘れている箇所が多かったので非常に助かりました。

後編もあるのでそちらも参考にしました。こういう丁寧な記事が書ける人は本当すごい....


#### Route53, RDS, ECSなど

https://zenn.dev/hibriiiiidge/books/49ee4063b10cec1df1a2

Next + Ruby on Railsの記事ですが、Circle CIの導入からAWS Route53、RDS、ECS、SSLの設定など大いに参考にさせていただきました。

フロント側、バックエンド側のDockerfileについてもコードが記載されていたので、こちらを参考にカスタマイズしながら開発環境を構築した覚えがあります。


#### S3(静的ウェブサイトホスティング), CloudFrontなど
https://zenn.dev/kou_taka/articles/40070e74004ee2

https://zenn.dev/wakkunn/articles/66a6e8372611dc

https://qiita.com/kiyokiyo_kzsby/items/77bdb81a1ce1852b30ca


S3の静的ウェブサイトホスティングにVueのフロント側を配置したのですが、独自ドメイン取得やCloudFrontの設定など参考にさせていただきました。
存在は知っていたのですが、こんなに簡単にページを公開できるかと感動した覚えがあります。

デプロイ用のコマンドの作り方まで記載していたので、もう少し自分もできることがあったなあと…



### 最後に
記事を書くまでにだいぶ期間が空いてしまいましたが、これから同じような技術スタックでアプリを作ろうする人の参考になればと嬉しいです。
ただ当時自分は中途半端に実務経験があったこともあり、完全未経験ではないので初学者が網羅的に学ぶ場合には、あまり役に立たない記事だったかもしれません。

また未経験の方のポートフォリオレベルがさらにハイレベルになっていて参考にならなかったら申し訳ないです。
自分の「こんなことやったよ！」「ちょっとだけ褒めて！」っていう範囲を抜け出せていないと思います。
ご了承ください……………

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594063/f08d08cc-ad38-7a52-d651-3a7e7e2c1cca.png" width=80% >




お粗末なアプリということは否定しませんが、業務と並行してある程度形になったアプリ作ったということはインプット、アウトプットに関して自信に繋がったと思います。
<b>業務でも初めて触れる技術でも臆せずよりチャレンジできるようになれたと思います。</b>

何より<b>インプットだけでは学習にはなっていないということを再度認識できたのが大きかったです。 </b>
インプットというコンフォートゾーンから脱出することの大切さ……!!

これから実務ではNuxtやGoも触れていくので勉強していかないとなと

最後まで読んでくださりありがとうございました。