Vue.jsとGo言語で簡単なWebアプリを作成する（Dockerで開発環境構築）

初投稿です。
過去の経験の復習かねて、書き起こしました。

## 概要
成果物はこちらになりますので、VueやGoを始めてみたい方への力になれれば幸いです。
https://github.com/aocm/vue-go-spa-sample

方針としては、なるべくローカルを汚さないことを最優先に考えました。
開発者ごとに環境が違うのも大変ですし、他のプロジェクト（自分の個人開発・仕事）で他のバージョンを利用していたら思わぬ影響が出てしまうのも嫌ですので、直接ローカルにインストールするのはなるべく避けたいです。

今回はDockerを使って、コンテナ内部で開発環境を作って作業していきます。
~~ただ、Gitについてはローカル（ホスト）でしか利用できない環境になってしまったので、そこは課題です。~~
~~（最低限、更新差分はコンテナの中でも確認できるようにしたほうが親切でした。）~~
（追記 2022/4/8）
.gitディレクトリを共有することで確認できるようにしました。（[該当PR](https://github.com/aocm/vue-go-spa-sample/pull/22)）

## 対象読者
- Dockerは最低限環境構築できているけど、Webアプリ開発ってどこから手を付ければいいんだ？という人
- Dockerで開発環境を構築するサンプルが見たい人
- Goの簡単な動くサンプルが見たい人
- Vueの簡単な動くサンプルが見たい人

※きれいなコードを書くことが目的ではないのでご了承ください

## 書くこと
- なるべくローカルを汚さないような開発環境構築の方法
    - ローカルを汚す...ここでは、各プラグインやソフトウェアをローカルにインストールしまくる状態を指します
    - プロジェクトによってバージョンが違うなど色々障害があると思うので、それの対策です
- 簡単なアプリ作成
    - VueのSPAサンプル
    - Go（Echo）のREST APIサーバー
- （おまけ）クロスコンパイルしてWindows上でやまびこアプリを起動してみる

## ここで書かないこと
- 本番環境のセッティング、CI/CD、テスト
- 本番環境を意識したリスク管理
- 言語の基礎説明
- Git、GitHubの基本的な使い方

※気が向いたら、時間をつくって別の記事に書くかもしれません

## 必要事項
- Docker・docker-composeが使えること
- Gitが使えること
- VSCodeをインストールしていること

### 各種バージョン（執筆時点）
- Windows10 Pro（1909）
    - 2004であればHomeであってもWSL2を使ってできます
- Docker（19.03.8）
- Git（2.27.0）
- VSCode（1.47.2）
- VSCodeの拡張機能
    - Docker（任意）
    - Remote Container
    - Go（コンテナ内部で利用）
    - Vuter（コンテナ内部で利用）

（追記1）
コンテナ内部の設定やモジュールを用いて開発することができますので、ローカルにはGo言語もNodeJSもインストールしたりPathを通したりする必要がありません。

（追記2）
Remote Containerは必須ではありませんが、とても便利な拡張機能です。
下図のように、ローカルからVSCodeのRemote Containerでそのコンテナの内部のファイルシステムにアクセスできるようになり、ローカルと変わらない感覚でコンテナ内開発を行うことができます。
※作ったサンプルはdocker-composeでマウント設定しているので、コンテナ内のソースコード修正がローカルにも反映されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417134/7534343f-4dfb-7dab-7543-d7d82e969d2b.png)
こちらの画像の出典および詳細： https://code.visualstudio.com/docs/remote/containers 

----

# 実際の作業
こんな感じですすめました。

1. ゴール設定
2. Git管理の開始
3. 環境構築
4. VueのSPA作成（Vue Cliでプロジェクト立ち上げまで）
5. GoのやまびこAPIサーバーの作成
6. SPAとAPIの連携

## ゴール設定
### Must
- Git管理できること
- ブラウザで入力した文字列を戻すだけのやまびこアプリができること

### Optional  
- 文字列をDBに登録できること
- 過去の入力したすべての文字列をブラウザで閲覧できること

~~（終わらなかったので、後日やります。たぶん。）~~
（追記 2022/4/8）
単純なDBへの登録と取得機能追加しました。（[該当PR](https://github.com/aocm/vue-go-spa-sample/pull/22)）



## Git管理の開始
- Gitプロジェクトの作成

```
> mkdir vue-go-spa-sample
> cd vue-go-spa-sample

> git init
Initialized empty Git repository in C:/xxxxxxxxxxxx/repository/aocm/vue-go-spa-sample/.git/

> git commit --allow-empty -m "first commit"
[master (root-commit) 2df398f] first commit
```

- GitHubにリモートリポジトリの作成、Push
    - https://github.com/aocm/vue-go-spa-sample


## 環境構築
- Vue用、Go用、MySQL用のDockerfileを作成して、docker-compose.ymlで一括管理
    - ファイルの中身については割愛
    - 基本的に枠だけ用意して、あとからポチポチメンテナンスしてこうというスタンスなDockerfileです
    - 理想はcompose upのタイミングでなるべくすませるべきです。（作業者によって開発環境が変わらないようにするために、ある程度自動で準備ができるようにするべき）

```
> docker ps
CONTAINER ID        IMAGE                      COMMAND                  CREATED             STATUS              PORTS                                NAMES
6491b0eb8e96        vue-go-spa-sample_client   "docker-entrypoint.s…"   9 minutes ago       Up 8 minutes        0.0.0.0:8080->8080/tcp               vgs-client
42d9f8f675b9        vue-go-spa-sample_server   "bash"                   27 minutes ago      Up 8 minutes        0.0.0.0:8000->8000/tcp               vgs-server
e8a1de1c140d        vue-go-spa-sample_db       "docker-entrypoint.s…"   27 minutes ago      Up 8 minutes        33060/tcp, 0.0.0.0:33060->3306/tcp   vgs-db
```

## VueのSPA作成
- RemoteContainerで立ち上げます  
   ※画像のフォルダマークをクリックすると立ち上がります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417134/855c345b-bdf4-718f-a624-f774dafd1285.png)

前述の通り、RemoteContainerは必須ではないですが、コンテナ内部で開発（ローカルを汚さない）ためにあると便利である拡張機能です。

**以降は、基本的には各コンテナの中で作業しています**

Vue Cliのインストール

```
/usr/src/app # npm install -g @vue/cli
```

アプリケーションの作成
※それなりに時間がかかりますので、待っている間Goの方に着手しました。
※カスタマイズについては、好みとか目的によります。今回はRouterだけ使いたいが今後手を加えることを考えていろいろと好みで追加しています。

```
/usr/src/app # vue create vue-spa
Vue CLI v4.4.6
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, Linter, Unit
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Airbnb
? Pick additional lint features: Lint on save
? Pick a unit testing solution: Jest
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
```

```
success Saved lockfile.
Done in 100.17s.
⚓  Running completion hooks...

📄  Generating README.md...

🎉  Successfully created project my-project.
👉  Get started with the following commands:

 $ cd my-project
 $ yarn serve
```

指示にしたがって起動してみます。

```
DONE  Compiled successfully in 14361ms 
App running at:
  - Local:   http://localhost:8080/ 
```

Dockerのポートを解放しておいてあるので、ホストのほうで http://localhost:8080/ をブラウザで開いてみて、画像のように表示されていれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417134/d432aabe-3111-4116-9dfc-1cba556e39e9.png)


## GoでAPI作成
RemoteContainerで立ち上げます。コンテナ内部にVSCodeサーバーを立ち上げてリンクします。

立ち上がり次第、コンテナ内部のVSCodeでGoの拡張機能をインストールします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417134/87ca30db-2fa0-9ef3-dc36-53a94c4e6d6e.png)

インストールしたら、Ctrl + Shift + P で command palette 開いて 「Go: Install/Update tools」 します。
すべてチェックして実行したら、下記のログがでるまで待ちます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417134/dd0bd02a-724c-b8bf-4dcc-5109ab9e2864.png)

```
.
.
Installing github.com/sqs/goreturns SUCCEEDED
Installing golang.org/x/lint/golint SUCCEEDED

All tools successfully installed. You are ready to Go :).
```

簡単なGoファイルを作って実行します。

```golang

package main

import (
	"fmt"
)

func main() {
	fmt.Println("####### start #######")
}
```

```
root@42d9f8f675b9:/go/src/github.com/aocm/vue-go-spa-sample# go run main.go 
####### start #######
```

問題なく動きそうなので、EchoでRestAPIサーバーを立てます。

```golang

package main

import (
	"github.com/aocm/vue-go-spa-sample/handler"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

func main() {
	// Echoのインスタンス作る
	e := echo.New()

	// 全てのリクエストで差し込みたいミドルウェア（ログとか）はここ
	e.Use(middleware.CORS())
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// ルーティング
	e.POST("/yamabiko", handler.YamabikoAPI())
	e.OPTIONS("/yamabiko", handler.OptionsCheck())

	// サーバー起動
	e.Start(":8000")
}
```

```golang

package handler

import (
	"net/http"

	"github.com/labstack/echo"
)

// YamabikoParam は /yamabiko が受けとるJSONパラメータを定義します。
type YamabikoParam struct {
	Message string `json:"message"`
}

// YamabikoAPI は /api/hello のPost時のJSONデータ生成処理を行います。
func YamabikoAPI() echo.HandlerFunc {
	return func(c echo.Context) error {
		param := new(YamabikoParam)
		if err := c.Bind(param); err != nil {
			return err
		}
		return c.JSON(http.StatusOK, map[string]interface{}{"hello": param.Message})
	}
}

```


## SPAとAPIの連携
VSCodeの拡張機能のVuterをインストールします

### 登録画面を作成
Yamabiko.vueをつくっていきます。

```html
<template>
  <div>
    <h1>This is an Yamabiko page</h1>
    <input v-model="message" placeholder="Say Yahho">
    <button @click="Send">Send</button>
    <p>Yamabiko : {{ yamabiko }}</p>
  </div>
</template>

<script>
export default {
  name: 'Yamabiko',
  data() {
    return {
      message: '',
      yamabiko: '',
    };
  },
  methods: {
    async Send() {
      const yamabiko = await this.CallYamabikoAPI().then((res) => res.json());
      this.yamabiko = yamabiko.message;
      window.alert(this.yamabiko);
    },

    async CallYamabikoAPI() {
      const url = 'http://localhost:8000/yamabiko';
      const data = {
        message: this.message,
      };
      try {
        return await window.fetch(url, {
          method: 'POST',
          headers: {
            'X-Requested-With': 'csrf', // csrf header
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(data),
        });
      } catch (e) {
        console.log(e);
        return e;
      }
    },
  },
};
</script>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417134/1f4875e8-05f3-7c9c-55c7-aa3374c624a9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417134/c53910db-8947-4c17-62b5-85b6f55fa1a2.png)

Mustの「ブラウザで入力した文字列を戻すだけのやまびこアプリができること」が達成できました！

## おまけ
### Linux向けにビルドして、コンテナ内部で実行してみる
```
root@f83b97e201a6:/go/src/github.com/aocm/vue-go-spa-sample# GOOS=linux GOARCH=amd64 go build main.go 
root@f83b97e201a6:/go/src/github.com/aocm/vue-go-spa-sample# ./main

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.1.16
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8000
```

フロントからも利用することができましたのでOKです。

### Windows向けにビルドしてホストで実行してみる
portが被ると失敗しますので、番号を8001に変更した後ビルドしてみます

```
root@f83b97e201a6:/go/src/github.com/aocm/vue-go-spa-sample# GOOS=windows GOARCH=386 go build -o yamabiko.exe main.go
```
Windowsで実行してみるとこのように窓が立ち上がり、実際に動くことが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417134/a8e3e8cb-bf07-4787-0a7e-e122ce4a8010.png)

（簡単にクロスコンパイルするのほんとすごい。。。）

---

## おわりに
久しぶりにプログラム書いたのでところどころ詰まるところがありました。
この記事とプログラムの考案・コーディングに合計8時間くらいかかってしまったので、学習を習慣づける・コーディング力を高める・利用できるアセットを増やすためにもう少し軽めに、情報量を狭く深くアウトプットしつづけたいなとも思いました。

かなり改善点が多いコードなのでプロダクトに利用できる状態じゃないのも事実であり、アセット化するためにももっと時間をかけたいです。

それにしても、やりたいこと知りたいことに対して時間が圧倒的に足りないですね。
もし読者様の作業時短になれていれば幸いです。もし無駄なお時間になっていたらすみません。

間違っていること、質問、アドバイスおよび応援コメントなどコメントいただけると幸いです。

## 参考
- Qiitaより
    - [Vue.js を vue-cli を使ってシンプルにはじめてみる](https://qiita.com/567000/items/dde495d6a8ad1c25fa43#comments)
    - [Go の echo ってWebサーバーでサクッと REST しちゃう](https://qiita.com/ezaki/items/62e806ae42828bb3567a)
    - [Go言語のWebフレームワーク「Echo」を使ってみる ②（リクエストパラメータの扱い）](https://qiita.com/y_ussie/items/2d397e70bfc38f75ca51)
- GoをWindows実行形式でコンパイル https://deeeet.com/writing/2013/12/12/go-build-for-windows/