ChatGPTを使って簡単なSPAを実装すると、どのくらい時間がかかるのか

# 1. はじめに

最近、生成AIを活用したアプリ開発の記事をよく目にするようになり、今更ながら生成AIを使ったコーディングに興味を持ちました。そこで、ChatGPTを使って簡単なTodoアプリを実装し、どれくらいの時間で完成するかをタイムアタック形式で試してみることにしました。フロントエンドにはVue.js、バックエンドにはGo、データベースにはPostgreSQLを使用しています。

結論としては、約5時間で完成しました。完成したアプリのクオリティを見て、「このクオリティでも5時間もかかるのか」と驚きつつも、不慣れな部分が多くて詰まりながら進めた中で、なんとか5時間でできたことに感心しています。

完成品は[こちら](https://github.com/utibori-jp/simple-todo)のリポジトリにアップロードしていますので、興味があればぜひご覧ください。

# とりあえずやってみる

まずは、早速ChatGPTにお願いしてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/769595/65bcc8a5-066b-651c-1f1b-f9007d91406c.png)

結果として、HTML、CSS、JavaScriptのファイルが出力されました。回答の全文を確認したい場合は、以下の折りたたみ要素を開いてチェックしてみてください。
<details><summary>ChatGPTの回答全文</summary>

----------------------------------------------------------------------------------------------
Todoアプリを作成するための基本的な手順を説明します。以下は、HTML、CSS、JavaScriptを使ったシンプルなTodoアプリの例です。

### 1. HTMLファイル（index.html）

```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todoアプリ</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <h1>Todoリスト</h1>
        <input type="text" id="todo-input" placeholder="タスクを追加...">
        <button id="add-todo">追加</button>
        <ul id="todo-list"></ul>
    </div>
    <script src="script.js"></script>
</body>
</html>
```

### 2. CSSファイル（styles.css）

```css
body {
    font-family: Arial, sans-serif;
    background-color: #f4f4f4;
    margin: 0;
    padding: 20px;
}

.container {
    max-width: 400px;
    margin: auto;
    background: white;
    padding: 20px;
    border-radius: 5px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

h1 {
    font-size: 24px;
}

input {
    width: calc(100% - 100px);
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 3px;
}

button {
    padding: 10px;
    margin-left: 10px;
    background-color: #5cb85c;
    color: white;
    border: none;
    border-radius: 3px;
    cursor: pointer;
}

button:hover {
    background-color: #4cae4c;
}

ul {
    list-style-type: none;
    padding: 0;
}

li {
    padding: 10px;
    border-bottom: 1px solid #ddd;
    display: flex;
    justify-content: space-between;
}

li button {
    background-color: #d9534f;
}
```

### 3. JavaScriptファイル（script.js）

```javascript
document.getElementById('add-todo').addEventListener('click', function() {
    const todoInput = document.getElementById('todo-input');
    const todoText = todoInput.value.trim();

    if (todoText) {
        const todoList = document.getElementById('todo-list');
        const listItem = document.createElement('li');
        listItem.textContent = todoText;

        const deleteButton = document.createElement('button');
        deleteButton.textContent = '削除';
        deleteButton.addEventListener('click', function() {
            todoList.removeChild(listItem);
        });

        listItem.appendChild(deleteButton);
        todoList.appendChild(listItem);
        todoInput.value = '';
    }
});
```

### 使い方

1. 上記のコードをそれぞれ `index.html`, `styles.css`, `script.js` という名前のファイルに保存します。
2. `index.html` をブラウザで開くと、Todoアプリが表示されます。
3. タスクを入力して「追加」ボタンを押すと、タスクがリストに追加されます。
4. 各タスクの「削除」ボタンを押すと、そのタスクが削除されます。

これで基本的なTodoアプリが完成です！質問や改良したい点があれば教えてください。

**ここまでがChatGPTの回答です。**

---------------------------------------------------------------------------------------
</details>


それぞれをコピペして動かしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/769595/2ee15b9b-fc13-c659-4e09-0482c1294771.png)

**問題なく動きました！これでTodoアプリ完成です！**

...

これではあまりにも面白くないので、もう少しハードルを上げてみることにしました。

# ゴールを設定する

少しハードルを上げて、フロントエンドとバックエンドを分けてSPA（シングルページアプリケーション）として作成することにしました。さらに、いくつかの詳細を決め、最終的に以下の条件を満たすTodoアプリを作成することを目標にしました。

- Todoアプリは[SPA](https://qiita.com/shinkai_/items/79e539b614ac52e48ca4)で実装する。
- データはバックエンドのインメモリではなく、データベース（DB）で永続化する。
- フロントエンドはVue.js、バックエンドはGolang、データベースはPostgreSQLを使用する。
- CRUDの4機能（Create, Read, Update, Delete）をそれぞれ実装する。
- DockerとDocker Composeを使用して環境構築を行う。

ちなみに、フロントエンド、バックエンド、データベースの技術選定は「最近触った技術だから」という理由だけで、特に深い理由はありません。

# いざタイムアタック

ゴールが決まったので、早速タイムアタックを開始します。スタートは昼過ぎで、今日中に完成させることを目標にしています。

:::note warn
Chat履歴を全て削除してしまったため、作成の過程を詳しく書けませんでした。代わりに、作業中に詰まったポイントをメモとして残しておきます。
:::

## 作成していて詰まったポイント

### 1. Golangが`docker build`できない

開発に使用していたPCがM1 Mac（Apple Silicon）だったため、ビルド時にエラーが発生していました。Dockerコンテナ内で実行されるには、Linux向けのバイナリが必要です。しかし、Goのビルド時には必ずしもLinux向けバイナリが生成されるわけではなく、デフォルトではホストOSの環境に依存してしまいます。

私が使っていたM1 Macでは、Apple Silicon向けのバイナリが生成され、それが原因でその後の処理が失敗していました。

この問題を解決するためには、バイナリ生成時に明示的にLinux向けの設定を指定する必要があります。以下のように変更することで、ビルドが正常に進むようになりました。

```bash
RUN go build -o main .
```

この部分を次のように変更します。

```bash
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main .
```

### 2. Vue Devtoolsが使えない

Vueでフロントエンドを作成する際、非常に便利なデバッグツールとして[Vue Devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=ja)があります。しかし、`docker-compose up --build`を実行してフロントエンドを確認した際に、このツールがうまく動作しませんでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/769595/70a0a5a8-958f-bff2-a27d-0d1464d47ff2.png)

原因は、アプリケーションを`production`モードでビルドしていたことにありました。`production`モードでは、アプリケーションの最適化のために、開発ツールやデバッグ情報が無効化されるため、Vue Devtoolsが利用できないのです。

Dockerfileでは、以下のコマンドでビルドを行っています。

```bash
RUN npm run build
```

この部分を開発モードに変更すれば、Vue Devtoolsを確認できるようになりますが、プロダクションモードの方が適切だと感じたため、このままにしました。開発時はローカルの環境で動かすことにしました。

この時点で知ったのですが、開発時は毎回Dockerを起動してビルドするのではなく、ローカル環境に開発用の設定を整え、その環境でソースコードを動かすのが一般的なようです。

[`production`モードでもVue Devtoolsを使えるようにする方法](https://qiita.com/POPOPON/items/60010faae1eb4e4e67ac)はあるようですが、ここでは省略させていただきます。

# そして完成

僕の不手際で制作過程でのやり取りが全くないですが、最初に設定したゴール通り、Docker環境で、SPAを作成することができました。ここからは、完成したWebアプリについて、簡単に説明していきます。

## レポジトリ構成

まずはレポジトリの構成について説明します。今回は、フロントエンドとバックエンドを一つのレポジトリにまとめたモノレポ形式で作成しました。

フロントエンドは、`npm create`コマンドを使用して生成される一般的な構成になっています。これにより、標準的なVue.jsプロジェクトのファイル構成が整えられ、すぐに開発を始められるようになっています。

一方、バックエンドはファイル数が少ないため、非常にシンプルな構成になっています。将来的に規模が大きくなった場合、[Go standards](https://github.com/golang-standards/project-layout?tab=readme-ov-file)に従って、より複雑な構成に拡張していく予定です。

```
todo-app/
├── backend/              # バックエンドのコード
│   ├── main.go           # エントリーポイント
│   ├── handlers/         # APIハンドラー
│   │   └── todo.go       # Todoに関するハンドラー
│   ├── models/           # Todoモデル
│   │   └── todo.go       # Todoモデル定義
│   ├── database/         # データベース接続関連
│   │   └── db.go         # データベース接続処理
│   └── Dockerfile        # バックエンドのDockerfile
├── frontend/             # フロントエンドのコード
│   ├── src/              # Vue.jsのソースコード
│   │   ├── components/   # Vueコンポーネント
│   │   ├── views/        # ビュー
│   │   ├── App.vue       # アプリケーションのメインコンポーネント
│   │   └── main.js       # エントリーポイント
│   ├── public/           # 公開リソース（index.htmlなど）
│   │   └── index.html    # アプリケーションのHTMLエントリーポイント
│   ├── nginx.conf        # Nginxの設定ファイル
│   ├── package.json      # プロジェクトの依存関係とスクリプトを管理するファイル
│   └── Dockerfile        # フロントエンドのDockerfile
├── db/                   # データベース設定（Docker Compose用）
│   └── init.sql          # 初期データのSQLスクリプト
├── docker-compose.yml    # Docker Compose設定ファイル
├── go.mod                # go.mod
├── go.sum                # go.sum
└── README.md             # プロジェクトの説明書
```

## Frontendについて
フロントエンドのディレクトリは分けてありますが、実際にコードを追加したのは`App.vue`ファイルのみです。アプリケーションがシンプルだったので、すべて`App.vue`にまとめても問題ないと判断し、そのままにしました。この形だと、全てのコードを一度に選択してコピー＆ペーストできるので、ChatGPTへの質問もスムーズに行えました。

CSSに関しては、「この辺をいい感じにして」というざっくりした指示でも、思った通りに仕上げてくれて感動しました。JavaScriptは基本的に理解している程度で、正しい形がわからない部分もあったため、評価は難しいですが、要件に合わせて関数を定義することで最低限の変更で済ませました。もっと全体最適化を意識したコードを出力させていれば、どうなったのか気になります。

## Backendについて
Todoアプリを迅速に作ることを目指していたため、バックエンドは簡素な作りになっています。せっかくなので簡単な単体テストくらいは書いておけば良かったと感じています。全体的にコード量が少ないので、まあ仕方がない部分もありますが、`database/db.go`の中で接続の確立部分とDB操作部分を分けた方がよかったかもしれません。

また、`DB`ディレクトリが`init.sql`しかないのはさみしいので、今後追加していきたいと考えています。

# 終わりに
記事を書くことには最初は懐疑的でしたが、試してみた結果、アウトプットの効果を実感しました。具体的なメリットとしては以下の3点が挙げられます。

1. 学びを言語化することで理解が深まる
2. 不完全な理解を改めて調べる機会が生まれる
3. 次回はもっと良いものを作ろうというモチベーションが高まる

これらは「アウトプットのメリット」としてよく言われていることですが、実際に自分で体験してみるとその効果が実感できます。今後も定期的に学びをアウトプットするために記事を書こうという気持ちになりました。

タイトルと内容が少しズレてしまいましたが、最後まで読んでいただき、ありがとうございました。