Astroとスプレッドシートで作るTodoアプリ

（現段階での）最終目標はAstroを利用して勤怠管理Webアプリを作りたい。
https://qiita.com/kuma_radcof/items/826bd0956ef8d55a8705
→やってる最中でメリット・デメリットがわかってきて何かの変更があるかもしれない。

どんな作品を作っていくにも、基本のCRUD機能実装レベルがまだまだと思っているのでそのレベルアップを図る意味でもTodoアプリを作ってみることにした。
現在の自分としてはCRUD機能ってなんぞやみたいなところはわかるし、書いてあるコードを読んでいくのは多少できるけど、一人で丸っと実装するまでではないという感じ。
言語を限定してGASとスプレッドシートを利用した環境ならCRUDできる。
APIも叩くことはできるけど、機能の実装という部分では特にPOSTメソッドをうまく使えていないという実感があった。

## 技術選定の理由
まずはAstroを使っていきたいというのが上記記事にもある通りのこと。
じゃあデータベースをどうするかという部分だけど、AstroDBは触れたことがあったのと最初はGASと連携？みたいな疑問からスタートしたのもあり、スプレッドシートをデータベースのように扱ってみようと思った。

## 出来上がったもの
- フロント画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524273/180be9a4-f248-0f0d-84f9-9dd84934f243.png)
- データベース画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524273/93757cb4-fb8a-9501-5afd-14388eddf2b0.png)

フロント画面のインプットタグに入力したテキストが、スプレッドシートのA2セルから下に蓄積されるというシンプルなものにした。

- コード
トップページ：スタイリングはTailwindを利用
```astro:src>page>index.astro
---
import Layout from '../layouts/Layout.astro';

---

<Layout>
	<div class="flex flex-col items-center">
		<div class="container mx-auto bg-gray-400 p-5 mt-20 rounded-lg">
			<h1 class="text-xl font-bold text-center">Todoアプリ</h1>
		</div>
		<form action="./api/postdata" method="POST" class="container flex flex-col items-center gap-8">
			<div class="flex items-center gap-8 w-10/12 mx-auto">
				<label for="contents">やること</label>
				<input type="text" name="inputContents" id="contents" class=" w-5/6 border rounded-lg border-gray-200 min-w-0 grow my-3 py-1.5 pl-1 pr-3 text-base text-gray-900 placeholder:text-gray-400 focus:outline focus:outline-0 sm:text-sm/6">
			</div>
			<button type="submit" class="px-6 py-2 bg-blue-500 rounded-lg">送信</button>
		</form>
	</div>
</Layout>

```

API
```astro:postdata.ts
import {google} from "googleapis";
import path from "path";

// プロジェクトのルートディレクトリからのパスを動的に生成
const keyFilePath = path.resolve(process.cwd(), "スプレッドシートにアクセスするための鍵ファイル");

export async function POST({request}: {request:Request}){
    try{
        // フォームからのデータ取得
        const formData = await request.formData();
        const tasks = formData.get("inputContents");

        console.log(tasks);
        
        // 取得したタスクが何もなかった時の処理
        if(!tasks){
            const body = JSON.stringify({
                success: false, //リクエストの失敗
                message: "タスクは空です",  //メッセージ内容
            })
            const statusObject = {
                status: 400,
                headers: {
                    "Content-Type": "application/json",
                }

            };
            return new Response(body, statusObject);
        }

        // 取得した内容が空ではなかったときにスプレッドシートへ書き込む
        // スプレッドシートにアクセスするためにGoogle APIを叩いて連携
        const auth = new google.auth.GoogleAuth({
            keyFile:keyFilePath,
            scopes: ["https://www.googleapis.com/auth/spreadsheets"],
        })

        // スプレッドシートへのアクセス
        const authApplication = google.sheets({version: "v4", auth});
        const spreadsheetId = "スプレッドシートのID";
        const range = "todos!A2:A";

        await authApplication.spreadsheets.values.append({
            spreadsheetId,
            range,
            valueInputOption: "USER_ENTERED",
            requestBody: {
                values: [[tasks]]
            }
        })
        // レスポンスを返す
        return (new Response(null, {
            status: 303,
            headers: {
                Location: "/"
            }
        }));

    } catch (error) {
        return new Response(JSON.stringify({ success: false, message: error.message }), {
            status: 500,
            headers: { "Content-Type": "application/json" },
        });
    }
}
```

## 学習の中での気づき・疑問・学び
### そもそもデータの受け渡しってどうするんだ？
ブラウザ側（フロント）でフォームタグ作って、入力できる箇所を用意してフォームのaction属性にpostを指定して、submitのボタンで送信するというのは言語化できているのだけど、そのデータはどこで受け取る？どうデータベースに渡す？というのが疑問だった。
そして個人開発していると誰にどうやって聞いたらいいんだ問題が出てくるわけだけど、時代の進歩もあってchatGPT先生に聞きながらやってみることに。

>ベテランのフロントエンドエンジニアとして振る舞ってください。
>Astroを利用してtodoアプリを作っています。
>form要素にmethod="post"を指定し、インプット属性を用意したところまでは良いのですが
>今後の希望として
>・スプレッドシートをデータベースとして扱いたい
>・フォーム要素内のインプット要素にテキストを入れ、送信ボタンを押すとスプレッドシートにデータが保管されるようにしたい。
>・保管されたデータ以外にフォームが用意されているページに登録したタスクが表示されるようにしたい。
>こういった希望を叶えるために、エンドポイント・APIの作成についてどのようにしたら良いのか悩んでいます。
>・Astroサイトの中でapiディレクトリを作り、tsファイルでエンドポイントを作るのか
>・スプレッドシート側の処理でdoPost関数を作成し、デプロイすれば良いのか
>APIやエンドポイントについて理解しきれていない部分があるので詳しく教えてほしいです。
>
といった感じで質問をしてみると
- `method="POST"`、`action="API用のファイルパス"`にしてデータを渡すことができる
- APIファイルではPOST関数を作ることで受け取ったデータを任意のところへ渡すことができる
- データを渡す際に、GoogleAPIの認証をしたりスプレッドシートへのアクセス権限を付与するといった下準備が必要になる
- 最終的にはResponseオブジェクトを返す必要がある

というのがわかってきました。
ちなみに、GASでも何かプログラムを組む必要があるのかと思っていましたが、そんな必要はなく編集の権限があれば良いということがわかりました。
スプレッドシートを利用することはテーブルやフィールドといった定義だとかマイグレーションの必要はないけど、Google cloudを利用してAPIの連携やDrive API、Sheets APIの連携、認証のための鍵ファイルを作成するといった手順が発生するので大人しくDBサービスを使った方が良いんだとわかった笑

### フォームデータの受け渡しにおける考え方
- フォームデータをリクエストオブジェクトとして受け取る
- 受け取ったデータからname属性の値に合うものをgetする
- getしたものが何もなければエラーのレスポンスを返す
- getしたものに何かふくまれていればデータベース（スプレッドシート）へのデータ渡しを試みる
なおスプレッドシートへのアクセスやデータのSetvalueについては`spreadsheetId`と`range`の文言を使わないとエラーになるので注意が必要だった
```
 // スプレッドシートへのアクセス
    const authApplication = google.sheets({version: "v4", auth});
    const spreadsheetId = "12jftxZtEcWXoyRl47wiakcmfmUAp9FtiGMKcvHeWgjc";
    const range = "todos!A2:A";

    await authApplication.spreadsheets.values.append({
        spreadsheetId,
        range,
        valueInputOption: "USER_ENTERED",
        requestBody: {
            values: [[tasks]]
        }
    })
```

全体の流れについては理解できたけど、POSTメソッドをpostと書いてしまっていたりしてうまくいかないことも経験。

ただ、基本的な機能の学習ができたので、あとはいろんなデータを扱ってみたり、投稿したものをフロント側にも表示させてみたりということをしていけばもっとプロジェクト作りが楽しくなりそう。おしまい。