ChatGPTによるAWS Serverless Applicationチュートリアル

アイキャッチ画像

こんにちは。2023 年 4 月に入社した K.A です。
最近、AI の進歩は本当にすごいですよね。 ChatGPT などの便利なツールが出てきて、私も毎日使っています。

私が 4 月に入った現場では、AWS を使ったサーバーレスアプリケーションの開発を行っています。ただサーバーレスは、私にとって新しい技術で、まだ完全にはキャッチアップできていません。

そこで、ネットで良い学習教材を探してみたのですが、自分にぴったりのものが見つからなくて困っていました。

そこでふと思いついたのですが、AI の力を借りれば、自分の勉強したい技術要素を使ったチュートリアルが作れるのではないかと思いました。この記事では、それが実際にできるのか試してみます。

## ChatGPT とは

今回は、ChatGPT を利用してチュートリアルを作ってみようと思います。
ChatGPT は、OpenAI が開発した自然言語処理を使って文章生成や質問応答など、さまざまなタスクをこなすことができる AI ツールです。
私は、ChatGPT Plus という有料プランに課金しているので、「GPT-4」という ChatGPT の最新モデルを今回使用していこうと思います。

## ChatGPT を使ってチュートリアルを作成してみる

まずは、プロンプトを作成する必要があります。
プロンプトとは、ChatGPT が回答を生成するための命令文のことです。
ChatGPT は、プロンプトの出来によって、回答の品質が変わってしまうようです。どのようにプロンプトを作成するべきな悩みましたが、いろいろ調べながら、以下のようなプロンプトを作成しました。

ChatGPTへの入力内容

あなたは優秀なエンジニア講師です。
これから指定した技術要素を使ったチュートリアル教材を作って欲しいです。
チュートリアルでは、シンプルなTODOアプリケーションを作成してください。

## 技術要素
・バックエンド:AWS SAM, API gateway, AWS Lambda, Dynamodb, Node.js
・フロントエンド: React.js

## TODOアプリの要件
1. タスクの追加:ユーザーは新しいタスクを追加できます。タスクには、タイトル、詳細、締め切り日などの情報を含めることができます。
2. タスクの表示:ユーザーは、すべてのタスクを一覧表示できます。タスクの詳細を確認することもできます。
3. タスクの編集:ユーザーは、タイトル、詳細、締め切り日などの情報を編集できます。
4. タスクの削除:ユーザーは、不要なタスクを削除できます。
5. タスクの完了:ユーザーは、完了したタスクにマークを付けることができます。完了したタスクは、タスク一覧から非表示になります。

## 制約条件
・技術要素を使用したことがない初心者が理解できるように、わかりやすく説明してください。
・コメント付きのソースコードを出すようにしてください。
・ディレクトリ構造がわかるようにしてください。
・必要に応じて、ターミナルで実行できるコマンドを教えて下さい
・内容に応じて見出しを付けてください。
・マークダウンで出力してください。
“`

## ChatGPT が作成してくれたチュートリアル

想像以上にしっかりとしたチュートリアルを作成してくれました!
ただ細かいところを指摘すると、制約条件で書いた「コメント付きのソースコードを出すようにしてください。」という指示は守られていないですね。笑

チュートリアル詳細

# シンプルな TODO アプリチュートリアル

このチュートリアルでは、シンプルな TODO アプリケーションを作成します。技術要素としてバックエンドには AWS SAM, API Gateway, AWS Lambda, DynamoDB, Node.js を、フロントエンドには React.js を使用します。

## 目次

1. [環境の準備](#環境の準備)
2. [バックエンドの構築](#バックエンドの構築)
3. [フロントエンドの構築](#フロントエンドの構築)
4. [デプロイ](#デプロイ)

## 環境の準備

### 必要なソフトウェアのインストール

このチュートリアルでは、以下のソフトウェアがインストールされていることを前提にしています。

– Node.js (v14 以上)
– npm (v6 以上)
– AWS CLI (v2 以上)
– AWS SAM CLI (v1.0 以上)

これらのソフトウェアがインストールされていない場合は、公式ドキュメントに従ってインストールしてください。

– [Node.js](https://nodejs.org/)
– [npm](https://www.npmjs.com/)
– [AWS CLI](https://aws.amazon.com/cli/)
– [AWS SAM CLI](https://aws.amazon.com/serverless/sam/cli/)

### AWS 設定

AWS CLI がインストールされていることを確認したら、次に AWS アカウントを設定します。ターミナルで以下のコマンドを実行してください。

“`bash
aws configure
“`

アクセスキー、シークレットアクセスキー、リージョン、出力形式を入力します。リージョンは、このチュートリアルで使用する AWS サービスが利用可能なリージョンを指定してください。例えば、`us-east-1` や `ap-northeast-1` などです。

## バックエンドの構築
 ### プロジェクトの初期化

まず、プロジェクト用のディレクトリを作成し、その中に移動します。

“`bash
mkdir simple-todo-app
cd simple-todo-app
“`

次に、AWS SAM CLI を使用してバックエンド用のプロジェクトを初期化します。

“`bash
sam init –runtime nodejs14.x –name backend
cd backend
“`

 ### テンプレートファイルの編集

`template.yaml` ファイルを開き、以下のように編集してください。

“`yaml
Resources:
TodosTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: Todos
AttributeDefinitions:
– AttributeName: id
AttributeType: S
KeySchema:
– AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5

GetTodosFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: getTodos.handler
Runtime: nodejs14.x
Environment:
Variables:
TODOS_TABLE: !Ref TodosTable
Events:
GetTodosApi:
Type: Api
Properties:
Path: /todos
Method: GET

CreateTodoFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: createTodo.handler
Runtime: nodejs14.x
Environment:
Variables:
TODOS_TABLE: !Ref TodosTable
Events:
CreateTodoApi:
Type: Api
Properties:
Path: /todos
Method: POST

UpdateTodoFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: updateTodo.handler
Runtime: nodejs14.x
Environment:
Variables:
TODOS_TABLE: !Ref TodosTable
Events:
UpdateTodoApi:
Type: Api
Properties:
Path: /todos/{id}
Method: PUT

DeleteTodoFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: deleteTodo.handler
Runtime: nodejs14.x
Environment:
Variables:
TODOS_TABLE: !Ref TodosTable
Events:
DeleteTodoApi:
Type: Api
Properties:
Path: /todos/{id}
Method: DELETE

Outputs:
TodosApi:
Description: API Gateway endpoint URL for the todos
Value: !Sub “https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/todos/”
TodosTable:
Description: DynamoDB table for storing todos
Value: !Ref TodosTable
“`

これにより、DynamoDB テーブルの作成と、各エンドポイントに対応する Lambda 関数が定義されます。

 ### Lambda 関数の実装

`src` ディレクトリに移動し、以下の JavaScript ファイルを作成して、それぞれの Lambda 関数のコードを実装します。
#### getTodos.js

“`javascript
const AWS = require(“aws-sdk”)
const dynamo = new AWS.DynamoDB.DocumentClient()

exports.handler = async (event) => {
const params = {
TableName: process.env.TODOS_TABLE,
}

const result = await dynamo.scan(params).promise()

return {
statusCode: 200,
body: JSON.stringify(result.Items),
}
}
“`

#### createTodo.js

“`javascript
const AWS = require(“aws-sdk”)
const dynamo = new AWS.DynamoDB.DocumentClient()
const uuid = require(“uuid”)

exports.handler = async (event) => {
const data = JSON.parse(event.body)

const params = {
TableName: process.env.TODOS_TABLE,
Item: {
id: uuid.v1(),
title: data.title,
details: data.details,
dueDate: data.dueDate,
completed: false,
},
}

await dynamo.put(params).promise()

return {
statusCode: 200,
body: JSON.stringify(params.Item),
}
}
“`

#### updateTodo.js

“`javascript
const AWS = require(“aws-sdk”)
const dynamo = new AWS.DynamoDB.DocumentClient()

exports.handler = async (event) => {
const data = JSON.parse(event.body)
const id = event.pathParameters.id

const params = {
TableName: process.env.TODOS_TABLE,
Key: {
id: id,
},
UpdateExpression:
“SET title = :title, details = :details, dueDate = :dueDate, completed = :completed”,
ExpressionAttributeValues: {
“:title”: data.title,
“:details”: data.details,
“:dueDate”: data.dueDate,
“:completed”: data.completed,
},
ReturnValues: “ALL_NEW”,
}

const result = await dynamo.update(params).promise()

return {
statusCode: 200,
body: JSON.stringify(result.Attributes),
}
}
“`

#### deleteTodo.js

“`javascript
const AWS = require(“aws-sdk”)
const dynamo = new AWS.DynamoDB.DocumentClient()

exports.handler = async (event) => {
const id = event.pathParameters.id

const params = {
TableName: process.env.TODOS_TABLE,
Key: {
id: id,
},
}

await dynamo.delete(params).promise()

return {
statusCode: 200,
body: JSON.stringify({message: “Todo deleted successfully”}),
}
}
“`

これでバックエンドの実装が完了しました。

## フロントエンドの構築

### React アプリの作成

プロジェクトのルートディレクトリに戻り、React のプロジェクトを作成します。

“`bash
cd ..
npx create-react-app frontend
“`

### 必要なパッケージのインストール

`axios` をインストールして、API と通信できるようにします。

“`bash
cd frontend
npm install axios
“`

### コンポーネントの作成

`src` ディレクトリ内に、以下のコンポーネントを作成します。

#### TodoList.js

“`javascript
import React, {useState, useEffect} from “react”
import axios from “axios”

const TodoList = () => {
const [todos, setTodos] = useState([])

useEffect(() => {
getTodos()
}, [])

const getTodos = async () => {
const response = await axios.get(““)
setTodos(response.data)
}

return (
<div>
{todos.map((todo) => (
<div key={todo.id}>
<h2>{todo.title}</h2>
<p>{todo.details}/<p>
<p>{todo.dueDate}/<p>
/<div>
))}
/<div>
)
}

export default TodoList
“`

**注:** `` をバックエンドの API エンドポイントに置き換えてください。

#### TodoForm.js

“`javascript
import React, {useState} from “react”
import axios from “axios”

const TodoForm = () => {
const [title, setTitle] = useState(“”)
const [details, setDetails] = useState(“”)
const [dueDate, setDueDate] = useState(“”)

const handleSubmit = async (e) => {
e.preventDefault()
const newTodo = {
title: title,
details: details,
dueDate: dueDate,
}

await axios.post(““, newTodo)

setTitle(“”)
setDetails(“”)
setDueDate(“”)
}

return (
<form onSubmit={handleSubmit}>
<label>Title:</label>
<input
type=”text”
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<label>Details:</label>
<input
type=”text”
value={details}
onChange={(e) => setDetails(e.target.value)}
/>
<label>Due Date:</label>
<input
type=”date”
value={dueDate}
onChange={(e) => setDueDate(e.target.value)}
/>
<button type=”submit”>Add Todo</button>
</form>
)
}

export default TodoForm
“`

**注:** `` をバックエンドの API エンドポイントに置き換えてください。

### コンポーネントの組み込み

`src/App.js` を開いて、以下のように編集してください。

“`javascript
import React from “react”
import TodoList from “./TodoList”
import TodoForm from “./TodoForm”

function App() {
return (
<div>
<h1>Simple Todo App</h1>
<TodoForm />
<TodoList />
</div>
)
}

export default App
“`

これでフロントエンドの実装が完了しました。

## デプロイ

### バックエンドのデプロイ

バックエンドのディレクトリに移動し、以下のコマンドを実行して AWS にデプロイします。

“`bash
cd backend
sam build
sam deploy –guided
“`

デプロイが完了したら、API エンドポイントをメモしておいてください。

### フロントエンドのデプロイ

フロントエンドのディレクトリに移動し、以下のコマンドを実行してビルドします。

“`bash
cd ../frontend
npm run build
“`

ビルドが完了したら、`build` ディレクトリを適当のウェブサーバーにアップロードしてデプロイします。例えば、Amazon S3 に静的ウェブサイトとしてホスティングすることができます。詳細については、[公式ドキュメント](https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html)を参照してください。

これでシンプルな TODO アプリケーションが完成しました。フロントエンドの URL にアクセスして、タスクの追加、表示、編集、削除、完了を試してみてください。

ただ、見た感じしっかりしていたとしても、実際に動くものが作成できているかはあやしいので、実際にチュートリアルをやっていこうと思います

## 実際にチュートリアルをやってみる

「必要なソフトウェアのインストール」と「AWS 設定」のところは、僕の環境ではすでに完了していたので、スキップしました。

`sam init –runtime nodejs14.x –name backend` を実行すると、以下のような選択肢が出てきました。どちらを選択したらいいかわからないので、GPT に聞いてみます。

“`
Which template source would you like to use?
1 – AWS Quick Start Templates
2 – Custom Template Location
“`

すると、このような回答が返ってきました。

“`
すみません、その点を明確にしていませんでした。

このチュートリアルでは、AWS Quick Start Templates(1番)を使用しています。したがって、プロンプトで `1` を選択して、`Enter` キーを押して進めてください。
“`

教えてもらった 1 を選択すると、また別の選択肢が出てきました。再度、質問を繰り返して進んでいきます。
ちょっとした説明は抜けてしまうようですね。ただ、質問しながら進められるのでいい感じです。

進めていくなかで、template.yaml の内容を編集するところがあったのですが、記述内容に関する解説がなかったので、GPT に聞いてみました。

_質問_

“`
template.yamlの記述内容の意味がわからないので、初心者でもわかるようにコードにコメントを付けて行く形で解説してもらってもいいですか?各プロパティごとに、コメントを付けるようにしてください。
“`

_回答_

“`
もちろんです。以下に、`template.yaml` の各プロパティにコメントを追加した内容を示します。

Resources:
# DynamoDB テーブルの定義
TodosTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: Todos
AttributeDefinitions:
# プライマリキーとなる id 属性の定義
– AttributeName: id
AttributeType: S
KeySchema:
# プライマリキーとして id 属性を使用する設定
– AttributeName: id
KeyType: HASH
ProvisionedThroughput:
# 読み取り容量と書き込み容量の設定
ReadCapacityUnits: 5
WriteCapacityUnits: 5
“`

すぐ先生に質問ができる環境で学習できている感じですね。
その後も、説明不足なところ、理解できないところなど GPT に質問しながら進めていき、バックエンドの構築が完了しました。

一度、ローカル環境で動作確認をしたかったので、その方法を聞いてみました。
ただ教えてもらった方法で、動作確認をしたところ、モジュールが見つからないエラーが発生していました。エラーメッセージをコピペして質問を繰り返すと解決することができました。

_エラー解決質問の回答 1_

““
このエラーメッセージは、`uuid` モジュールが見つからないことを示しています。Lambda 関数のコードで `uuid` モジュールを使用しているため、まずは `uuid` モジュールをインストールする必要があります。

バックエンドディレクトリに移動して、以下のコマンドを実行して `uuid` モジュールをインストールしてください。

“`bash
cd backend
npm init -y
npm install uuid
““

これで `uuid` モジュールがインストールされます。再度、`sam local start-api` コマンドを実行してローカル API を起動し、Postman でテストを行ってください。

“`

*エラー解決質問の回答2*
“`

エラーがまだ発生している場合、`uuid` モジュールが正しく Lambda 関数に含まれていない可能性があります。この問題を解決するには、`template.yaml` ファイルを更新して、Lambda 関数が `node_modules` ディレクトリを含むようにします。

1. `template.yaml` ファイルを開きます。
2. すべての Lambda 関数定義の `CodeUri` プロパティを `src/` から `.`(カレントディレクトリ)に変更します。これにより、`node_modules` ディレクトリが Lambda 関数に含まれるようになります。

例:

yaml
Resources:
GetTodosFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: src/getTodos.handler # …省略…

“`

他にも、CORSのところでのエラーやDynamoDBを操作するため、IAM ロールに適切なポリシーが付与されていないため生じるエラーなどありましたが、それらのエラーも、GPTとの対話の中で解決でき無事デプロイして動かすところまでできました!

## まとめ
今回は、ChatGPTを使って、自分の勉強したい技術要素を使ったチュートリアルを作成しました。
不具合も多々ありましたが、内容としてはしっかりとしたものが作れていたのではないかと思います。不具合も、GPTとの対話の中で解決できましたし、理解できないところなども詳しく質問できるのは良い体験で、学習効率が良いなと思いました。
ただ、GPTの弱点として、2021年までの情報しか持っていないため最新情報に対応していないということもあり、DynamoDBの部分のコードの書き方が古い(AWS SDK for JavaScript バージョン 3 ではなく、バージョン2の書き方になっている)というものがありました。
この弱点が克服されるとさらに良いチュートリアルが作れそうですね。
ChatGPTを使って学習するのはおすすめなので、ぜひ試してみてください。