ChatGPT 4oと対話しながらwebサービスを個人開発する話 その①

# これまでの内容
前回の記事では、ChatGPTと対話しながらWebサービスを開発する様子を紹介しました。  
おかげさまで600以上の閲覧がありました。

前回の記事はこちら

https://qiita.com/travelclass0606/items/135263b166a0db7ca988

この時は、AWSのEC2上にフロントエンド（React）、バックエンド（Node.js）を構築し、CloudFront経由で配信するシンプルな環境でした。  
次はサーバーレスの開発に挑戦してみます。

↓ちなみにこの時に開発したWebサービスはこちらです。

https://travel-class.com

---

# 題材

イベントやチケットなどの申請方法や入手方法のマニュアルを作成し、共有できるWebサービスを作りたい。

## きっかけ

妻の実家に行った際、義母が大阪万博でブース鑑賞の申請をWebで行うのにとても苦労している様子を見て、誰かがわかりやすいマニュアルを作成し、共有できるサービスがあれば助かると感じました。  
鑑賞するブースの指定を何回もやり直しており、トータルで2時間くらいやっていました……。

---

# ChatGPTの使い方 土台作り

## 1. 初めにやりたいイメージを言語化

まずは、自分のやりたいことを簡単に言語化し、ChatGPTに投げかけます。今回は以下のような文章を使いました。

### やりたいこと：

申請方法や入手方法のマニュアルを作成し、共有するWebサービスを作りたい。

### きっかけ：

母が大阪万博で鑑賞するブースの申請をWebで行う際に、とても苦労しており、誰かがわかりやすいマニュアルを作成し、共有できるサービスがあれば助かるなと感じた。

### 開発条件：

- 環境：AWS (S3, Lambda, DynamoDB)
- フロントエンド：React
- バックエンド：Node

## 2. 必要な機能と画面の仕様

以下に、それぞれの画面や機能の詳細をまとめました。

### トップページ：

- 投稿されたものを閲覧ビューが多い順に10件を表示（それぞれ冒頭の数行を表示）
- 投稿の詳細閲覧ボタン、検索ボタン（ワード、カテゴリ別）、投稿ボタン

### 投稿ページ：

- 文字入力と途中に写真も掲載できる仕様
- 入力しやすいUIを目指す

### 投稿確認ページ：

- 投稿ページで入力した内容の確認を行う
- 問題なければ「確定」、修正する場合は「修正」ボタンを押す

### 検索結果一覧ページ：

- トップページと同様に、閲覧ビューが多い順に10件ずつ表示
- 「次へ」ボタンで次の10件を表示、「戻る」ボタンで前の10件に戻る

### 投稿詳細ページ：

- 投稿内容を閲覧できる
- 閲覧ビュー数をカウントして表示
- コメント機能（ニックネームとメッセージ）を追加

### 補足：

- ユーザー登録はなし
- より適切な方法があれば、ChatGPTからの提案を受け入れる

## 3. イメージの言語化は完璧でなくてOK

最初のイメージの言語化は、完璧でなくても問題ありません。とりあえず書き出してみることが大切です。  
その後、ChatGPTとブレインストーミングをしながらアイデアを深めていきましょう。

## 4. ChatGPTからの返答

ChatGPTに上記の内容を投げた結果、得られた回答は以下の通りです。

---

## Lambda関数とDynamoDB連携のコード例

### 1. 環境の準備

まず、以下の手順でLambda関数を作成します。

1. **AWSコンソールでLambda関数を作成**:
   - ランタイム: Node.js 18.x（最新のバージョン）
   - ロール: DynamoDBにアクセス権限のあるIAMロールを使用

2. **AWS SDKのインストール**（ローカルでテストする場合）:
   ```bash
   npm install aws-sdk
   ```

3. **API Gatewayとの接続**:
   - 各Lambda関数に対して、API Gatewayを設定します。

### 2. DynamoDBとの連携コード

#### 2.1. GET `/manuals` (マニュアルの一覧を取得)

```javascript
// DynamoDBからマニュアル一覧を取得
const AWS = require('aws-sdk');
const dynamoDb = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event) => {
  const { category, query } = event.queryStringParameters || {};
  let params = {
    TableName: 'ManualsTable',
    Limit: 10,
  };
  
  // フィルタリング条件の設定
  if (category) {
    params.FilterExpression = 'category = :category';
    params.ExpressionAttributeValues = { ':category': category };
  }
  if (query) {
    params.FilterExpression = 'contains(title, :query)';
    params.ExpressionAttributeValues = { ...params.ExpressionAttributeValues, ':query': query };
  }

  try {
    const data = await dynamoDb.scan(params).promise();
    return {
      statusCode: 200,
      body: JSON.stringify(data.Items),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Error retrieving manuals', details: error.message }),
    };
  }
};
```

#### 2.2. GET `/manuals/{id}` (特定のマニュアルを取得し、ビュー数をインクリメント)

```javascript
// マニュアルの取得とビュー数の更新
exports.handler = async (event) => {
  const { id } = event.pathParameters;
  const getParams = {
    TableName: 'ManualsTable',
    Key: { manualId: id },
  };

  try {
    const data = await dynamoDb.get(getParams).promise();
    if (!data.Item) {
      return {
        statusCode: 404,
        body: JSON.stringify({ error: 'Manual not found' }),
      };
    }

    // ビュー数をインクリメント
    const updateParams = {
      TableName: 'ManualsTable',
      Key: { manualId: id },
      UpdateExpression: 'SET views = views + :increment',
      ExpressionAttributeValues: { ':increment': 1 },
      ReturnValues: 'UPDATED_NEW',
    };
    await dynamoDb.update(updateParams).promise();

    return {
      statusCode: 200,
      body: JSON.stringify(data.Item),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Error retrieving manual', details: error.message }),
    };
  }
};
```

#### 2.3. POST `/manuals` (新しいマニュアルを作成)

```javascript
// 新しいマニュアルの作成
const { v4: uuidv4 } = require('uuid');

exports.handler = async (event) => {
  const { title, content, category } = JSON.parse(event.body);
  const params = {
    TableName: 'ManualsTable',
    Item: {
      manualId: uuidv4(),
      title,
      content,
      category,
      views: 0,
      createdAt: new Date().toISOString(),
    },
  };

  try {
    await dynamoDb.put(params).promise();
    return {
      statusCode: 201,
      body: JSON.stringify({ message: 'Manual created successfully', manualId: params.Item.manualId }),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Error creating manual', details: error.message }),
    };
  }
};
```

#### 2.4. POST `/comments` (コメントを追加)

```javascript
// コメントの追加
exports.handler = async (event) => {
  const { manualId, nickname, message } = JSON.parse(event.body);
  const params = {
    TableName: 'CommentsTable',
    Item: {
      commentId: uuidv4(),
      manualId,
      nickname,
      message,
      createdAt: new Date().toISOString(),
    },
  };

  try {
    await dynamoDb.put(params).promise();
    return {
      statusCode: 201,
      body: JSON.stringify({ message: 'Comment added successfully' }),
    };
  } catch (error) {
    return {
      statusCode: 

500,
      body: JSON.stringify({ error: 'Error adding comment', details: error.message }),
    };
  }
};
```

### 3. デプロイとテスト

1. Lambda関数をそれぞれAWSコンソールにアップロードするか、AWS CLIを使用してデプロイします。
2. API Gatewayを使用して各エンドポイントに対してリクエストをテストします。
3. DynamoDBのテーブルが正しくデータを格納しているかを確認します。

これで、マニュアルの作成、閲覧、コメント投稿などの機能を持つLambda関数を実装できました。次はReactフロントエンドと連携して、実際に動作するWebサービスとして構築していきます。

---

関数やスクリプトなどの土台となるものができました。  
次はエンドポイントなどの情報を加えて、AWSにデプロイします。細かい調整は、ChatGPTに変更したい内容を伝えつつ進めていきます。　続く…