Webアプリ開発ほぼ未経験エンジニアがWebアプリの環境構築を試してみた

# はじめに
リンクラフト株式会社の竹内です。
リンクラフトアドベントカレンダーの16日目を担当します。

最近、Webアプリを開発するための環境構築を私的に試しましたので、今回はそちらの紹介をさせていただきます（無知レベルで構築していますので、いろいろ適切ではない部分もあるかと思いますが、そこはご容赦を）。



# なぜ環境構築を試したのか
私は社会人になって以降、ずっとエンジニアとして活動してきたのですが、Webアプリの開発業務に携わる機会がほぼ無かったんですよね（バッチとかのバックエンド側の開発が主でして）。
長年経験してきてバックエンド一筋、はさすがに武器として貧弱と感じており、フロント側のことも基礎ぐらいは抑えておきたいと思ったのが発端になります。
さらに言うと、業務だとどうしてもアプリケーション（ビジネスロジックとか）の部分だけ意識をして開発を進めている節があり、今回良い機会だったので「どうせなら基盤となる環境の構築からやってみるかー！」というノリでお試しに至ったわけです。

開発環境が整った暁には、個人的に使えるWebアプリの開発も計画しているので、結構気合は入ってマス。



# というわけで環境構築
では早速、手順の紹介を！・・・といきたいところですが
はじめに、構築するWebアプリのサーバー構成を紹介しておきます。
Dockerを用いて各サーバーをコンテナ化しています。

![スクリーンショット 2024-11-30 15.18.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3938403/23171734-f245-a8b3-7ac7-1b29d4914f37.png)

各コンテナの概要は以下になります。
* **Vueコンテナ**
ユーザーからのリクエストを受けます。今回はフロントエンドにVue.jsを使います。

* **APIコンテナ**
DBサーバーを操作するためのAPIを提供します。Vueコンテナは本サーバーが提供するAPIを利用してデータベースを操作します。ORMライブラリである`Sequelize`をインストールします。

* **DBコンテナ**
家計簿アプリのデータを保管するデータベースサーバーです。RDBMSは`MySQL`を使用します。

また、Dockerネットワーク（`backend`）を作成することで、コンテナ名でコンテナ間通信が行えるようにします。



## Dockerのインストール
ここからが環境構築の手順となります。まずはDockerを入れないと始まらないので、インストールしていきます。手順は以下を参考にしました。

https://sukkiri.jp/technologies/virtualizers/docker/docker-mac_install.html



## 作業ディレクトリ準備
PCの任意ディレクトリに、アプリの資材を配置するための作業ディレクトリを作成します。
私はMacで作業しているのですが、個人ユーザーディレクトリ配下に作業ディレクトリを作成しました。
アプリ名は`MoneyTracker`（仮称）とし、作業ディレクトリ名に採用しています。

```bash:
# ディレクトリ移動
$ cd /Users/[個人フォルダ]

# ディレクトリ作成
$ mkdir -p development/money_tracker/vue
$ mkdir -p development/money_tracker/api
$ mkdir -p development/money_tracker/db/logs
$ mkdir -p development/money_tracker/db/conf

# 設定ファイル、ログファイル生成
$ touch development/money_tracker/compose.yml
$ touch development/money_tracker/.env
$ touch development/money_tracker/db/logs/mysql-error.log
$ touch development/money_tracker/db/logs/mysql-query.log
$ touch development/money_tracker/db/logs/mysql-slow.log
$ touch development/money_tracker/db/conf/my.cnf
```


## 設定ファイル編集
### compose.yml
作業ディレクトリ準備時に作成した、`compose.yml`（Dockerのコンテナ定義ファイル）を編集していきます。
最終的に、以下内容としました。

<details><summary>compose.yml</summary>

```yaml:compose.yml
services:
  db:
    # 起動するイメージを指定
    image: mysql:8.0.32-debian
    platform: linux/amd64

    # 環境変数を設定
    environment:
      - MYSQL_ROOT_HOST=${DB_ROOT_HOST}
      - MYSQL_DATABASE=${DB_NAME}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_ROOT_PASSWORD=${DB_PASS}
      - TZ=${TZ}

    # ホスト側のポート:コンテナのポート
    ports:
      - '3306:3306'

    expose:
      - '3306'

    # ボリュームバインド
    volumes:
      - ${PWD_PATH}/db/conf:/etc/mysql/conf.d:ro
      - mysqldata:/var/lib/mysql/
      - ${PWD_PATH}/db/logs:/var/log/mysql/

    #使用するネットワーク
    networks:
      - backend

  api:
    image: node:14.21.2-buster
    build:
      context: .
      dockerfile: ./Dockerfile
    environment:
      - MYSQL_SERVER=db
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_DATABASE=${DB_NAME}
      - TZ=${TZ}
      - CHOKIDAR_USEPOLLING=true

    #コンテナを起動させ続けるよう設定
    tty: true

    ports:
      - '3000:3000'

    # ソースコードを格納するフォルダをマウント
    #（ホスト側の./apiをコンテナの/appにマウント）
    volumes:
      - ${PWD_PATH}/api:/app

    # 起動時のカレントフォルダを指定
    working_dir: /app

    # 起動後に実行するコマンドを指定
    command: sh -c "npm run dev"

    networks:
      - backend

    #依存関係（apiコンテナより先にdbコンテナが起動するように設定）
    depends_on:
      - db

  vue:
    image: node:14.21.2-buster
    build:
      context: .
      dockerfile: ./Dockerfile
    environment:
      - CHOKIDAR_USEPOLLING=true
    tty: true
    ports:
      - '8080:8080'
    volumes:
      - ${PWD_PATH}/vue:/app
    working_dir: /app/money-tracker
    command: npm run serve
    networks:
      - backend
    depends_on:
      - api

networks:
  backend:
    driver: bridge

volumes:
  mysqldata:
```

Dockerネットワーク（`backend`）については、`networks:`キーで指定しています。
</details>

`compose.yml`の記述形式について詳しく知りたい方は、以下が参考になると思います。

https://docs.docker.jp/v1.12/compose/compose-file.html



### my.cnf

MySQLで使用する文字コードや、ログ出力先の指定などを`my.cnf`に追記していきます。
この設定ファイルは、コンテナ起動時にボリュームマウントによりDBコンテナ上に配置されます。

<details><summary>my.cnf</summary>

```conf:my.cnf
# MySQLサーバーへの設定
[mysqld]
# 文字コード/照合順序の設定
character-set-server = utf8mb4
collation-server = utf8mb4_bin

# タイムゾーンの設定
default-time-zone = SYSTEM
log_timestamps = SYSTEM

# デフォルト認証プラグインの設定
default-authentication-plugin = mysql_native_password

# エラーログの設定
log-error = /var/log/mysql/mysql-error.log

# スロークエリログの設定
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 5.0
log_queries_not_using_indexes = 0

# 実行ログの設定
general_log = 1
general_log_file = /var/log/mysql/mysql-query.log

# mysqlオプションの設定
[mysql]
# 文字コードの設定
default-character-set = utf8mb4

# mysqlクライアントツールの設定
[client]
# 文字コードの設定
default-character-set = utf8mb4
```
</details>

`my.cnf`で設定できるシステム変数については、以下が参考になります。

https://dev.mysql.com/doc/refman/8.0/ja/server-system-variables.html



### .env
`compose.yml`内で参照する環境変数の設定を記述していきます。
具体的には、`compose.yml`でMySQLの環境変数を設定している部分が、このファイルから具体的な値を取得していることになります。

<details><summary>.env</summary>

```conf:.env
PWD_PATH=/Users/[ユーザ名]/development/money_tracker
DB_ROOT_HOST=%
DB_NAME=money_tracker
DB_USER=[MySQLユーザ名]
DB_PASS=[MySQLパスワード]
TZ=Asia/Tokyo
```
</details>



## Dockerコンテナの起動
ここで一度、Dockerコンテナを以下コマンドにて起動します。

```bash:
# Dockerコンテナ起動
$ cd /Users/[ユーザ名]/development/money_tracker
$ docker-compose up -d

# コンテナの起動状態確認
$ docker-compose ps
```
起動状態を確認すると、起動しているコンテナがリスト表示されます。
この時点で表示されたのはDBコンテナとAPIコンテナで、Vueコンテナは表示されませんでした。すなわち、DBコンテナとAPIコンテナは起動できており、Vueコンテナは起動できていないことがわかります。



## DBコンテナの準備
### MySQL接続確認
起動済のDBコンテナにアクセスし、MySQLに接続できるか確認します。
```bash:
# DBコンテナにアクセス
$ docker-compose exec db /bin/bash

#### ここからDBコンテナ内 ####
# MySQLに接続
$ mysql -u root -p
```
MySQL接続時にパスワードを聞かれますので、`.env`ファイルで設定したパスワード（`DB_PASS`）を入力してEnterキーを入力します。


### 文字コード確認
接続成功したら、MySQLの文字コード設定について確認します。
```bash
#### DBコンテナ内 ####
# 文字コード確認
mysql> show variables like 'char%';

+--------------------------+--------------------------------+
| Variable_name            | Value                          |
+--------------------------+--------------------------------+
| character_set_client     | utf8mb4                        |
| character_set_connection | utf8mb4                        |
| character_set_database   | utf8mb4                        |
| character_set_filesystem | binary                         |
| character_set_results    | utf8mb4                        |
| character_set_server     | utf8mb4                        |
| character_set_system     | utf8mb3                        |
| character_sets_dir       | /usr/share/mysql-8.0/charsets/ |
+--------------------------+--------------------------------+
8 rows in set (0.04 sec)
```
`my.cnf`で指定した値になっていれば、正常に反映されていることになります。


### データベース確認
続いて、データベースの状態を確認します。
```bash
#### DBコンテナ内 ####
# データベース確認
mysql> show databases;

+--------------------+
| Database           |
+--------------------+
| information_schema |
| money_tracker      |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.05 sec)
```
`money_tracker`というデータベースが存在していることが確認できます。
`money_tracker`の中身についても確認してみます。
```bash
#### DBコンテナ内 ####
# データベース切り替え
mysql> use money_tracker;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

# テーブル確認
mysql> show tables;
Empty set (0.00 sec)
```
`money_tracker`データベースの中身は空です（テーブルは1つも存在しません）。

テーブルの作成は手作業でも可能ですが、今回はAPIコンテナから`Sequelize`のDBマイグレーション機能を使用して行いますので、ここで確認作業を終了します。


### コンテナ切断
`quit`でMySQLを抜け、`exit`でDBコンテナも抜けます。
```bash
#### DBコンテナ内 ####
# MySQLログアウト
mysql> quit
Bye

# コンテナ切断
$ exit
```



## APIコンテナの準備
### パッケージインストール
続いて、APIコンテナで必要なパッケージをインストールしていきます。
#### Express
まずは`express-generator`パッケージをインストールし、実行します。
Expressは、Node.jsで利用できるWebアプリケーションフレームワークです。

https://expressjs.com/ja/

```bash
# APIコンテナにアクセス
$ docker-compose exec api /bin/bash

#### ここからAPIコンテナ ####
# express-generatorパッケージのインストール
$ npm install -g express-generator
$ express .
```


#### nodemon
次に、`nodemon`パッケージをインストールします。ソースコードを変更したときに、自動でサーバーを再起動してくれる便利なツールです。
```bash
#### APIコンテナ内 ####
# nodemonパッケージのインストール
$ npm install -D nodemon
```


#### Sequelize
さらに、Sequelizeに関連するパッケージをインストールします。その後、`sequelize-cli`を使用してSequelizeの初期化を行います。

https://sequelize.org/

```bash
#### APIコンテナ内 ####
# Sequelize関連のパッケージインストール
$ npm install mysql2 sequelize sequelize-cli

# Sequelizeの初期化
$ npx sequelize-cli init
```


### 設定ファイル編集
#### package.json
上記でインストールした`nodemon`については、開発環境でのみ使用するため、その設定を/app/package.jsonに反映します。`"scripts"`の部分に`"dev"`を追加します。
```bash
#### APIコンテナ内 ####
# package.jsonの編集
$ vi /app/package.json
```

<details><summary>package.json</summary>

```diff_json:package.json
{
  "name": "app",
  "version": "0.0.0",
  "private": true,
  "scripts": {
+   "dev": "nodemon ./bin/www",
    "start": "node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "^1.4.7",
    "cors": "^2.8.5",
    "debug": "^2.6.9",
    "express": "^4.16.4",
    "http-errors": "^1.6.3",
    "jade": "^1.11.0",
    "morgan": "^1.9.1",
    "mysql2": "^3.11.4",
    "sequelize": "^6.37.5"
  },
  "devDependencies": {
    "nodemon": "^3.1.7"
  }
}
```
</details>


#### config.json
上記でSequelizeの初期化を行った時に、`/app/config`に`config.json`というJSONファイルが作成されていますが、それを`config.js`に変更してJavascriptファイルにします。
```bash
#### APIコンテナ内 ####
# ファイル名変更
$ mv /app/config/config.json /app/config/config.js
```
そして、内容を以下のように書き換えます。

<details><summary>config.js</summary>

```js:config.js
module.exports = {
  development: {
      username: process.env.MYSQL_USER,
      password: process.env.MYSQL_PASSWORD,
      database: process.env.MYSQL_DATABASE,
      host: process.env.MYSQL_SERVER,
      dialect: 'mysql',
  },
  test: {
      username: process.env.MYSQL_USER,
      password: process.env.MYSQL_PASSWORD,
      database: process.env.MYSQL_DATABASE,
      host: process.env.MYSQL_SERVER,
      dialect: 'mysql',
  },
  production: {
      username: process.env.MYSQL_USER,
      password: process.env.MYSQL_PASSWORD,
      database: process.env.MYSQL_DATABASE,
      host: process.env.MYSQL_SERVER,
      dialect: 'mysql',
  },
};
```
</details>

この書き換えにより、`compose.yml`で設定した環境変数の値をそのままDB接続情報として使うことが可能となります。


#### index.js
上記で`config.json`→`config.js`にファイル名変更したので、`app/models/index.js`を開き、`const config =...`の部分を修正します（`config.json`を`config.js`に変更）。

```bash
#### APIコンテナ内 ####
# index.jsの編集
$ vi /app/models/index.js
```

<details><summary>index.js</summary>

```diff_javascript:index.js
'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const process = require('process');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
- const config = require(__dirname + '/../config/config.json')[env];
+ const config = require(__dirname + '/../config/config.js')[env];

const db = {};

let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config);
}

fs
  .readdirSync(__dirname)
  .filter(file => {
    return (
      file.indexOf('.') !== 0 &&
      file !== basename &&
      file.slice(-3) === '.js' &&
      file.indexOf('.test.js') === -1
    );
  })
  .forEach(file => {
    const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
    db[model.name] = model;
  });

Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;
```

</details>

このファイルはSequelizeによるDBマイグレーション実行に参照されます（今回は、以降使用しません）。


### コンテナ切断
`exit`でAPIコンテナを抜けます。
```bash
#### APIコンテナ内 ####
# コンテナ切断
$ exit
```



## Vueコンテナの準備
### コンテナ起動
本手順実行時点で、Vueコンテナは停止しています。
ここでは、`docker-compose run`を使ってvueコンテナを一時的に起動します。
```bash
# Vueコンテナの起動
$ docker-compose run --rm --no-deps vue /bin/bash
```
`--rm`は、コンテナ切断時に自動でコンテナを削除するオプションです。
`--no-deps`は、リンクしたサービスを起動しないようにするオプションです。`compose.yml`でコンテナの依存関係を定義したため、本来であればVueコンテナが起動する前にAPIコンテナ（さらにその前のDBコンテナも）が立ち上がりますが、このオプションを設定することでVureコンテナのみ起動するようにしています。


### パッケージインストール
#### Vue CLI
まずは`vue-cli`パッケージをインストールします。

https://cli.vuejs.org/

```bash
#### Vueコンテナ内 ####
# vue-cliパッケージのインストール
$ npm install -g @vue/cli
```

インストール後、プロジェクトの作成を行います。
```bash
#### Vueコンテナ内 ####
# プロジェクトの作成
$ vue create money_tracker
```

プロジェクト作成にあたり、いくつか設定を問われるため、以下の通り選択し設定を進めます。

`? Please pick a preset:`
　→ <font color="Red">**`Manually select features`**</font> を選択
 
`? Check the features needed for your project:`
　→ <font color="Red">**`Babel, Linter / Formatter, Router, Vuex`**</font> を選択
 
`? Choose a version of Vue.js that you want to start the project with`
　→ <font color="Red">**`2.x`**</font> を選択

`? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)`
　→ <font color="Red">**`n`**</font> を入力
 
`? Pick a linter / formatter config:`
　→ <font color="Red">**`ESLint with error prevention only`**</font> を選択
 
`? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)`
　→ <font color="Red">**`Lint on save`**</font> を選択
 
`? Where do you prefer placing config for Babel, ESLint, etc.?`
　→ <font color="Red">**`In dedicated config files`**</font> を選択
 
`? Save this as a preset for future projects? (y/N)`
　→ <font color="Red">**`N`**</font> を入力
 
`? Pick the package manager to use when installing dependencies:`
　→ <font color="Red">**`Use NPM`**</font> を選択

完了すると以下メッセージが表示されます。
```
🎉  Successfully created project money_tracker.
👉  Get started with the following commands:

 $ cd money_tracker
 $ npm serve
```


#### Axios
Axiosは、Node.jsとブラウザのためのPromiseベースのHTTPクライアントです。REST-APIを実行したいときなどに､これを使うと実装が容易になるようです｡

https://axios-http.com/ja/docs/intro

```bash
#### Vueコンテナ内 ####
# axiosパッケージのインストール
$ npm install axios
```


#### Vuetify
Vuetifyは整えられたマテリアルコンポーネント（誰にとっても分かりやすく使いやすいもの）を提供するVue UIライブラリです。後々使えそうなので、追加しておきます。

https://vuetifyjs.com/ja/

```bash
#### Vueコンテナ内 ####
# vuetifyの追加
$ vue add vuetify
```

ここでも設定について問われるため、以下の通り選択し設定を進めます。
`? Choose a preset:`
　→ <font color="Red">**`Default (recommended)`**</font> を選択

 以下のように表示されれば追加完了です。
 ```
✔  Successfully invoked generator for plugin: vue-cli-plugin-vuetify
 vuetify  Discord community: https://community.vuetifyjs.com
 vuetify  Github: https://github.com/vuetifyjs/vuetify
 vuetify  Support Vuetify: https://github.com/sponsors/johnleider 
 ```


### コンテナ切断
ここまで完了したら、`exit`でVueコンテナを抜けます。
```bash
#### Vueコンテナ内 ####
# コンテナ切断
$ exit
```



## 全てのコンテナを再起動
各コンテナのセットアップが一通り完了したので、一度コンテナを全て停止した後、再起動させます。
```bash
# 全てのコンテナを停止
$ docker-compose down

# コンテナ再起動
$ docker-compose up -d

# 起動状態確認
$ docker-compose ps
```
`docker-compose ps`実行時にVue, API, DBの全コンテナが表示されれば起動完了となります。


### ブラウザアクセス確認
ブラウザから http://localhost:8080/ にアクセスします。
![スクリーンショット 2024-12-01 1.51.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3938403/75ad41cb-7687-9d89-83b2-6a2760ea6fec.png)
このようにVue.jsの初期画面が表示されれば完了です！（お疲れ様でした！）
※私はここに至るまでに１週間かかりました←



# 開発計画しているアプリとは？
環境構築の手順としては以上となるのですが、冒頭にも書きました開発を計画しているアプリについても、おまけとして紹介させていただきます。
開発を計画しているアプリはズバリ、**家計簿アプリ**になります。

家計簿アプリを題材に選定した背景は以下の通りです。

---
<font color="Gray">
<i>我が家の家計管理はずっとエクセルを用いてきたのですが、以下のような問題点も抱えていました。

* オフライン（自宅PC）で管理・更新しているため、外出先とかでリアルタイムに更新できない
* フォーマットを作成したのが私であり、妻から見るとフォーマットが分かりにくく入力のハードルが高い（らしい）
* エクセルファイルの肥大化を回避すべく、年次でフォーマットを刷新（ファイルを新たに作成）しているが、地味に面倒
* 収入や支出の項目が増減する度に、フォーマットを更新しなければならない

<i>少し前から管理の方法は変えたいと思っており、Webアプリ化することで抱えている問題は概ね解決できそうだったので、今回の題材選定に至っています。
</font>

---

せっかく作るなら実用的なものが良く、使えるものを題材にした方が開発の熱量も上がると思いますし（希望的観測）。

現在、絶賛設計中なので、引き続き開発に取り組んでいけたらと思います（果たしていつ完成するやら）。







# さいごに
いかがでしたでしょうか？
今回はWebアプリの環境構築について紹介させていただきました。
アプリの基盤部分に触れる機会が無かった私としては、Webアプリの仕組みを理解するには非常に役立ったので、ひとまず満足しています。
実際に操作して動作しているものを目の当たりにした方が理解は深まると思いますし、コンソールでコマンドをバチバチ叩いていると「やってる感」が出ます（笑）


実際に動かしながらWebアプリ開発を学んでみたいという方の参考になれば幸いです…！

-----------
一緒に働く仲間を募集中です！

リンクラフト株式会社では、組織拡大に伴い積極的な採用活動を行っています。
少しでも興味がある方はぜひご連絡ください。

▽会社ホームページ
https://lincraft.co.jp/
▽Instagram
https://www.instagram.com/lincraft.inc/
▽ご応募はこちらより
https://lincraft.co.jp/recruit

※カジュアル面談も受付中です。ご希望の方はHPのお問い合わせフォームよりご連絡ください。

-----------