SvelteKitでPostgresSQLへデータの登録・取得をしてみる

# はじめに
SvelteKitは公式ドキュメントが（体感）読みやすいですね！
しかし、公式ドキュメントではDBを利用するにはどうすればいいのかが、
ふんわりとしか書かれていないため、実際に理解のためにSvelteKitからDB接続を試してみたいと思います。
個人的に理解した内容になるため、誤りやもっと良い方法などがあればコメント頂けると嬉しいです。

# 前提
- 開発環境：Windwos10
- 主なバージョン
    - SvelteKit：1.0.0
    - Node.js：v16.16.0
    - Postgresql：PostgreSQL 12.2, compiled by Visual C++ build 1914, 64-bit

# Postgresqlの準備
今回はDBに特にこだわりがなかったため、PostgreSQLを使用しました。
ローカル開発環境用にインストールする場合、次の記事が参考になると思われます。

https://qiita.com/tom-sato/items/037b8f8cb4b326710f71

# SvelteKitの準備
公式ドキュメントに従って、SvelteKitのプロジェクトを作成します。

https://kit.svelte.jp/docs/creating-a-project

# フロント画面の作成
今回は簡単なデータの登録・取得を試したいため、ふりかえりを記録できるアプリを作りたいと思います。
画面には、入力欄と入力結果一覧を表示させます。
画面の動きとしては、入力欄に入力した内容をDBに登録し、入力結果一覧をDBから取得していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/c19ce71b-9b58-3d16-930c-84838c5d6a1d.png)

# データの取得

なにかしらのデータをDBから取得して、画面上で使用したい場合は、まず「+page.svelte」にdata変数を定義します。

```html:+page.svelte
<script lang='ts'>
    import LookBackList from '$lib/component/LookBackList.svelte'
    import type { PageData } from './$types';
    export let data: PageData;
</script>
```

https://kit.svelte.jp/docs/routing#page-page-svelte

そして、DBから値を取得するには、load関数をexportする「+page.server.ts」ファイルを作成します。
このload関数の返り値が、「+page.svelte」のdata変数に格納されます。
```typescript:+page.server.ts
import type { PageServerLoad } from './$types';
import { getAllFromKptTable } from '$lib/server/db';

export const load = ( async () => {
    // DBから値を取得
    const allLookBack = await getAllFromKptTable();
    // 取得結果を整形して返却
    const keeps = allLookBack.filter((lookBack: any) => lookBack.type === 'Keep');
    const problems = allLookBack.filter((lookBack: any) => lookBack.type === 'Problem');
    const trys = allLookBack.filter((lookBack: any) => lookBack.type === 'Try');
    return {
        LookBacks: {
            keepLookBacks: keeps,
            problemLookBacks: problems,
            tryLookBacks: trys,
        },
    }
}) satisfies PageServerLoad;
```

https://kit.svelte.jp/docs/routing#page-page-server-js

DBからの値の取得には「node-postgres」というライブラリを使用します。
「src/lib/server/db.ts」ファイルを作成し、次のように修正します。

```typescript:db.ts
import pg from 'pg';

export const initDataBaseConnect = () => {
    return new pg.Client({
        database: "hoge", // データベース名
        user: "hoge",     // ユーザー名
        password: "hoge", // パスワード
        host: "127.0.0.1",
        port: 5432,
    });
}

export const getAllFromKptTable = async () => {
    const pgClient = initDataBaseConnect();
    await pgClient.connect();
    const result = await pgClient.query(`SELECT * FROM kpt`);
    await pgClient.end()
    return result.rows;
}
```

「node-postgres」の使用方法については、以下のサイトを参考にしました。

https://www.dailyupblog.com/backend_development/962/

ここまでの修正でDBから値を取得することができます。動作を確認してみましょう。
pgAdminを使用して、データを登録します。
```sql
INSERT INTO kpt VALUES ('Keep', 'Keepテスト', '001'), ('Problem', 'Problemテスト', '002'), ('Try', 'Tryテスト', '003');
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/5fc3fd14-ef28-a489-4110-9fae05a87522.png)

画面にてデータが正しく取得できるか確認してみます。完璧ですね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/4ccdd84b-41d4-0eff-a19d-c53309812e2f.png)

# データの登録

次に画面上でユーザが入力した値を使って、DBにデータの登録を試してみます。
画面上の「add」ボタンを押下するとリクエストが飛ぶように修正します。

```html:SendText.svelte
<script lang='ts'>
    // import分や変数定義、その他処理は省略...

    const sendInputText = () => {
        if (inputText.length > 0) {
            const param = {
                'lookBack': inputText,
                'lookBackType': lookBackType,
            };
            fetch(addLookBackUrl, {
                method: 'POST',
                body: JSON.stringify(param),
            }).then((res) => {
                inputText = '';
                dispatch('clickAddButton');
            })
        }
    }
</script>

<div>
    <input type='text' name='lookBack' bind:value={inputText} />
    <button on:click={sendInputText}>add</button>
</div>
```

リクエストに対応するサーバの処理を「src/routes/api/lookback/add/+server.ts」に記述していきます。
上記ファイルには、データの取得と同じ流れでDBに接続しますが、
DB接続用のClientのインスタンス生成については「src/hooks.server.ts」に記載し、インスタンスをevent.localsに格納します。
（必ずしもhooksでインスタンス生成する必要があるかは自信ないです。。。）

```typescript:src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
import { initDataBaseConnect } from '$lib/server/db'
 
export const handle = (async ({event, resolve}) => {
    event.locals.db = initDataBaseConnect();
 
    const response = await resolve(event);
    return response;
}) satisfies Handle;
```

```typescript:src/routes/api/lookback/add/+server.ts
import { json } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
import uuid from 'uuid';

export const POST = (async ({locals, request}) => {
    const data = await request.json();
    const newId = uuid.v4();
    await locals.db.connect();
    await locals.db.query(`INSERT INTO kpt(id, type, lookback) VALUES('${newId}', '${data.lookBackType}', '${data.lookBack}')`);
    await locals.db.end();
    return json({});
}) satisfies RequestHandler;
```

hooks.server.tsとは、リクエストの処理である「+server.ts」の処理の前に行うインターセプターみたいなものと理解しています。
詳しくは、公式ドキュメントを参照してください。

https://kit.svelte.jp/docs/hooks#server-hooks

これでDBへのデータ追加の処理が完成したため、動作を確認してみます。上手くいきました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/ed2c1a16-37b7-909b-eb10-7405502c7803.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031330/fde97b9a-8aae-9a91-ddae-87ebe7a72208.png)


# 最終的なフォルダ構成

今回作成したプロジェクトのsrc配下のフォルダ構成は次の通りになりました。

```md
src
 ├─lib
 │  ├─component               // コンポーネントの配置ディレクトリ
 │  │ ├─ LookBackList.svelte
 │  │ └─ SendText.svelte
 │  ├─images
 │  ├─server
 │  │ └─ db.ts                // DBの初期化処理を定義
 │  └─typescript
 ├─routes
 │  ├─api
 │  │ └─lookback
 │  │   ├─add
 │  │   │ └─ +server.ts    　  // レコード登録のAPI処理
 │  │   └─getByType
 │  │     └─ +server.ts   　   // Type別レコード取得のAPI処理
 │  ├─ +page.server.ts    　   // レンダリング前のデータ取得処理定義
 │  ├─ +page.svelte       　   // 画面定義
 │  └─ style.css
 └─ hooks.server.ts            // hook処理の定義
```

# まとめ

躓く箇所もあったりしましたが、無事にSvelteKitからDB接続をすることができて良かったです。
ドキュメントを読むだけでなく、手を動かすのがやはり勉強になりますね。

# 参考文献
- [SvelteKitドキュメント：https://kit.svelte.jp/docs/introduction](https://kit.svelte.jp/docs/introduction)（2023-01-18参照）