データベースCloudflare D1とAstroサイトの連携（前半：ローカル版）

## 目的

データベースCloudflare D1とAstroのサイトを連携します
今回の投稿の前半ではローカルのDBと接続し、別投稿の後半ではリモートのDB、つまりCloudflareのサーバー上のDBと接続します

## つくるもの

https://qiita.com/fujino_tech/items/e8bee3378fedb305e7c2

こちらをベースにします

メモ（「タイトル」と「内容」のセット）をデータベースから取得し、表示することが今回の実装部分です


## 本編

全体の流れは、
1. データベースを作成
1. データベース定義を記述し、テーブルを追加するSQLファイルを生成
1. SQLファイルを実行し、データベースにテーブルを追加
1. テーブルにデータを追加
1. Astroのエンドポイントにデータベースへのアクセス処理を追加

こんな感じです

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

データベース関連のパッケージ`drizzle-orm`、`drizzle-kit`と`better-sqlite3`、CloudflareのCLI`wrangler`をインストールします

```
npm install drizzle-orm drizzle-kit
npm install better-sqlite3
npm install wrangler
```

### データベースの準備
#### データベースの定義を作成

データベースの定義を作成し、SQLファイルを生成します

```js:src/db/schema.ts
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core';

export const memos = sqliteTable('memos', {
  id: integer('id', { mode: 'number' }).primaryKey({ autoIncrement: true }),
  title: text('title'),
  content: text('content'),
});
```

```
npx drizzle-kit generate --schema=./src/db/schema.ts --dialect=sqlite
```
↓　実行結果
```
> npx drizzle-kit generate --schema=./src/db/schema.ts --dialect=sqlite
drizzle-kit: v0.22.7
drizzle-orm: v0.31.2

1 tables
memos 3 columns 0 indexes 0 fks

[✓] Your SQL migration file ➜ drizzle\0000_flippant_sprite.sql 🚀
```

`drizzle\0000_flippant_sprite.sql`が生成され、以降の工程ではこれを使います

```sql:000_flippant_sprite.sql
CREATE TABLE `memos` (
	`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
	`title` text,
	`content` text
);
```

#### CLIでログインする

`npx wrangler login`でCLI経由でCloudflareにログインします

```
npx wrangler login
```

↓　実行結果

```
> npx wrangler login  

 ⛅️ wrangler 3.61.0
-------------------

Attempting to login via OAuth...
Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=XXXXXXXXX
Successfully logged in.
√ Would you like to help improve Wrangler by sending usage metrics to Cloudflare? ... no
Your choice has been saved in the following file: C:\Users\XXXXX\AppData\Roaming\xdg.config\.wrangler\metrics.json.

  You can override the user level setting for a project in `wrangler.toml`:     

   - to disable sending metrics for a project: `send_metrics = false`
   - to enable sending metrics for a project: `send_metrics = true`
```

以下のようにWebブラウザのログイン画面を経由します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821654/314c0fb5-fc05-be47-3cee-e20681fe161b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821654/74d4b0d4-5736-ce90-44bb-02dde49a9dc4.png)

#### データベースを作成する

CLIでデータベースを作成します

```
npx wrangler d1 create DB20240622
```
↓　実行結果
```
> npx wrangler d1 create DB20240622

 ⛅️ wrangler 3.61.0
-------------------

✅ Successfully created DB 'DB20240622' in region APAC
Created your new D1 database.

[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "DB20240622"
database_id = "xxxx-xxxx-xxxx-xxxx-xxxx"
```

実行すると、Cloudflareのダッシュボードから、`DB20240622`というデータベースがつくられていることが確認できます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821654/13c48da8-e8f6-19e7-4e63-e7b90ad6177f.png)

#### データベースのコンフィグファイルを作成

前述の操作で出力された設定記述を`wrangler.toml`を作成して、記載します

```toml:wrangler.toml
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "DB20240622"
database_id = "xxxx-xxxx-xxxx-xxxx-xxxx"
```
#### SQLファイルを実行

以下のコマンドで作成済みのSQLファイルを実行します
`--local`がローカルに対する操作を意味し、`--remote`だとCloudflareサーバー上の本番のデータベースに対して操作します

```
npx wrangler d1 execute DB20240622 --local --file=./drizzle/0000_flippant_sprite.sql
```
↓　実行結果
```
> npx wrangler d1 execute DB20240622 --local --file=./drizzle/0000_flippant_sprite.sql

 ⛅️ wrangler 3.61.0
-------------------

🌀 Executing on local database DB20240622 (9c560d0f-03b8-477b-93dc-9da3f3fc3c3e) from .wrangler\state\v3\d1:
🌀 To execute on your remote database, add a --remote flag to your wrangler command.
```

#### Drizzle Studio向けにデータベース情報をセットする

`drizzle.config.ts`を作成し、`url`に前述のコマンドで作成された`.wrangler/state/v3/d1/miniflare-D1DatabaseObject/XXXX.sqlite`を設定します

```ts:drizzle.config.ts
import { defineConfig } from "drizzle-kit"

export default defineConfig({
    dialect: "sqlite",
    dbCredentials: {
        url: ".wrangler/state/v3/d1/miniflare-D1DatabaseObject/5f5a9139cd06e3a10deff13ceaa03bbc42bab7d3570f17a28f257aac74162e52.sqlite",
    },
})
```

#### データベースをDrizzle Studioで確認する

`drizzle-kit`に含まれているDrizzle Studioを使い、Webブラウザからデータベースにアクセスしてみます

https://orm.drizzle.team/drizzle-studio/overview

コマンドは以下の通りです

```
npx drizzle-kit studio
```
↓　実行結果
```
> npx drizzle-kit studio    
drizzle-kit: v0.22.7
drizzle-orm: v0.31.2

No config path provided, using default path
Reading config file 'E:\github\20240622\drizzle.config.ts'

 Warning  Drizzle Studio is currently in Beta. If you find anything that is not 
working as expected or should be improved, feel free to create an issue on GitHub: https://github.com/drizzle-team/drizzle-kit-mirror/issues/new or write to us 
on Discord: https://discord.gg/WcRKz2FFxN

Drizzle Studio is up and running on https://local.drizzle.studio
```

`https://local.drizzle.studio`にアクセスすると、以下のようにデータベースを確認できます
作成済みの`memos`テーブルが存在することがわかります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821654/4aaba930-7ef0-b8b1-224a-a0e4f0db0c6f.png)

#### データベースにデータを追加しておく

続いて、データを追加してみます

```
npx wrangler d1 execute DB20240622 --local --command="insert into memos (title,content) values('Learn D1','contentcontent')"
```
↓　実行結果
```
> npx wrangler d1 execute DB20240622 --local --command="insert into memos (title,content) values('Learn D1','contentcontent')"

 ⛅️ wrangler 3.61.0
-------------------

🌀 Executing on local database DB20240622 (xxx-xxx-xx-xxxx-xxxx) from .wrangler\state\v3\d1:
🌀 To execute on your remote database, add a --remote flag to your wrangler command.
```

Drizzle Studioで確認すると、データが追加されています

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821654/eb3d8d7c-4a24-1f37-5046-444e8582448b.png)

ついでに、もう1件のデータを追加しておきました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821654/52e63056-afac-be7d-6e33-0240f0838b1a.png)

### Astroからデータベースへアクセス

#### AstroにCloudflareアダプター追加 

Cloudflareにアクセスできるようにアダプターを追加します
`@astrojs/cloudflare`を追加し、その後、`astro.config.mjs`へ設定を追加します

```
npx astro add @astrojs/cloudflare
```
↓　実行結果
```
> npx astro add @astrojs/cloudflare
✔ Resolving packages...

  Astro will run the following command:
  If you skip this step, you can always run it yourself later

 ╭──────────────────────────────────────────╮
 │ npm install @astrojs/cloudflare@^11.0.0  │
 ╰──────────────────────────────────────────╯

√ Continue? ... yes
✔ Installing dependencies...

  Check our deployment docs for @astrojs/cloudflare to update your "adapter" config.
  
   success  Configuration up-to-date.
```

設定は以下のように、`adapter: cloudflare()`を追加します

```js:astro.config.mjs
import { defineConfig } from 'astro/config';

import vue from "@astrojs/vue";
import cloudflare from "@astrojs/cloudflare";

// https://astro.build/config
export default defineConfig({
  integrations: [vue()],
  output: 'server',
  adapter: cloudflare()
});
```

#### エンドポイントへデータベースへのアクセス処理を追加

エンドポイントの処理では、`locals.runtime.env`から環境変数にアクセスできます
このとき、`.env.d.ts`に記述を追加しないといけない気がしますが、今のところは追加しなくとも動いているので、ひとまず無視しておきます

<details><summary>.env.d.tsに必要な気がする記載</summary>

```ts:.env.d.ts
/// <reference types="astro/client" />

type D1Database = import("@cloudflare/workers-types").D1Database;
type ENV = {
	DB: D1Database;
};

// use a default runtime configuration (advanced mode).
type Runtime = import("@astrojs/cloudflare").Runtime<ENV>;
declare namespace App {
  interface Locals extends Runtime {}
}
```
</details>

必要そうだったら、上記をご参考にどうぞ

`src/pages/v1/memo.ts`は以下のように書きました
データベースにデータが存在しなければダミーデータを使用するようにしていますが、書き方が適切かどうかはわかりません、適当です

```js:src/pages/v1/memo.ts
import { drizzle } from 'drizzle-orm/d1';
import { memos } from "../../db/schema";
import type { Runtime } from '@astrojs/cloudflare';

export async function GET({ locals }: { locals: Runtime }) {
    const envDB = locals.runtime.env.DB as D1Database;
    const db = drizzle(envDB);
    
    let memo = await db.select().from(memos);
    
    if (!memo || (Array.isArray(memo) && memo.length === 0)) {
        memo = [{ id: 1, title: "DUMMY1", content: "content1" }, { id: 2, title: "DUMMY2", content: "content2" }]
    }

    return new Response(
        JSON.stringify({
            body: { memos: memo }
        }), { status: 200 }
    );
}
```

### 動作確認

動作確認をしてみます

```
npm run dev
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821654/347711c4-c39c-4877-12dd-b2cc5d154aee.png)

よさそうです


## まとめ

以上で、ローカルのデータベースにアクセスして、データを取得することができました

後半ではリモートの、Cloudflareのデータベースに対して、処理をおこなってみます