Drizzle ORM でスキーマファイルを使ってDBを操作する方法

:::note
【Drizzle ORM シリーズ】
・Drizzle ORM を Next.js で使ってみる #Next.js - Qiita  
　https://qiita.com/sigeta/items/c0a2b0463eca06314906  
・Drizzle ORM でスキーマファイルを使ってDBを操作する方法 #Next.js - Qiita  
　https://qiita.com/sigeta/items/99a625c17c6a0a75da54  
・Drizzle ORM でリレーションを張る方法 #Next.js - Qiita  
　https://qiita.com/sigeta/items/b800f3e38beb1639d2b7  
・Drizzle ORM の設定ファイルを接続時も利用する方法 #TypeScript - Qiita  
　https://qiita.com/sigeta/items/d9267a06061d4d2a51e8
・Drizzle ORM でダミーデータを流し込む方法 #TypeScript - Qiita  
　https://qiita.com/sigeta/items/8bc85b74273eeb37c27a
:::


## はじめに

https://orm.drizzle.team/docs/kit-overview

`Drizzle ORM` には、DBを操作する CLI kit が付属している。
これを使って、スキーマ作成、マイグレーション、データの取得をやってみる。

まず初めに `drizzle-kit` をインストールしておこう。

```bash
$ npm i drizzle-kit
```


## 設定ファイル

https://orm.drizzle.team/docs/drizzle-config-file

`Drizzle ORM` を利用する際の設定を `drizzle.config.ts` に記載する。

```ts:drizzle.config.ts
import { defineConfig } from 'drizzle-kit'
import process from 'node:process'

process.loadEnvFile()

export default defineConfig({
    dialect: 'sqlite',
    schema: './db/schema.ts',
    out: './db/drizzle',
    casing: 'snake_case',
    dbCredentials: {
        url: process.env.DATABASE_URL!,
    },
})
```

- `dialect`: 利用するDBの種類を指定する
- `schema`: スキーマファイルを配置する場所を指定する
    - ここにテーブル定義を記載する
- `out`: `drizzle` のDB管理ファイルを配置する場所を指定する
    - このフォルダは編集禁止
- `casing`: `snake_case` を指定すると、列名を自動的にスネークケースに変換してくれる
- `dbCredentials`: DB接続情報
    - SQLite の場合は `url` にファイル名を記載する

`dbCredentials` の情報は `.env` ファイルに記載するとよい。
`process.loadEnvFile()` を使うことで `.env` の内容を読み取れる。

https://qiita.com/youtoy/items/ae838cacd43b3c3e61e1

```ini:.env
DATABASE_URL='test.sqlite'
```



## Drizzle ORM の初期化

`Drizzle ORM` は以下の画像のような構成になっている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/9015e8c1-99d6-4c1a-aacd-948866ae6301.png)

`schema.ts` にてテーブルを定義し、DBの状態は `drizzle` フォルダに吐き出して、ファイル管理する。
`drizzle` はいわば `package-lock.json` のようなもの。

なので、いったん `pull` を行って現時点の空のDBの状態を吐き出す。

```bash
$ npx drizzle-kit pull
[✓] 0 tables fetched
[✓] 0 columns fetched
[✓] 0 indexes fetched
[✓] 0 foreign keys fetched
[⣷] 0 policies fetching
[✓] 0 check constraints fetched
[✓] 0 views fetched

[✓] Your SQL migration file ➜ db/drizzle/0000_icy_adam_warlock.sql 🚀
[✓] You schema file is ready ➜ db/drizzle/schema.ts 🚀
[✓] You relations file is ready ➜ db/drizzle/relations.ts 🚀
```

結果以下のファイルが出力された。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/f5a407f8-74f4-4a53-8304-36bcef2b9c3e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/aff7abb8-ded5-49c3-9285-918555d1aea7.png)

スナップショットやマイグレーションのSQLファイルが生成される。
これらは管理ファイルのため、直接の操作はやめよう。


## スキーマファイルからマイグレーションを生成する

https://orm.drizzle.team/docs/sql-schema-declaration

一番簡単な方法として、スキーマファイルからマイグレーションを実施する。
今回は簡単なユーザーテーブルを用意する。

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

export const users = sqliteTable('users', {
  id: integer().notNull().primaryKey(),
  name: text().notNull(),
  age: integer(),

  created_at: integer({ mode: 'timestamp' }).notNull()
    .default(sql`(unixepoch())`),
  updated_at: integer({ mode: 'timestamp' }).notNull()
    .default(sql`(unixepoch())`)
    .$onUpdate(() => sql`(unixepoch())`),
})

export type User = typeof users.$inferSelect
```

用意したら `generate` コマンドを実行して、`migration` ファイルを生成する。

```bash
$ npx drizzle-kit generate
1 tables
users 5 columns 0 indexes 0 fks

[✓] Your SQL migration file ➜ db/drizzle/0001_daily_black_widow.sql 🚀
```

結果以下のファイルが出力された。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/775b86a4-e520-4d95-8731-294508e06aa8.png)

新しく `0001` に今回の差分（空 -> usersテーブル作成）が作成された。
中身を見てみると今回の変更用のSQLクエリや構造が記録されている。

ファイル名はランダムみたい。

最後に `push` コマンドにて、DBへ反映する。

```bash
$ npx drizzle-kit push
```

## DBを確認する

`Drizzle ORM` には `studio` という、DBのビューワーが付属している。
こちらを実行してDBの状況を確認したり、SQLを実行したりすることが可能。

```bash
$ npx drizzle-kit studio
```

=> https://local.drizzle.studio/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/57086520-e908-4e3a-971c-428c75814666.png)

試しに一行追加してみる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/b4cdfce8-2501-4dbb-9841-307dffc860fe.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/cb63f06b-f22c-495d-805c-919b0f453ab8.png)

編集もお手軽で文句なし！




## `Next.js` より利用する

最後に今回作成したデータを、`Next.js` より利用する。

設定ファイルを仕込んだ、DB接続用のファイルを作成する。
こちらを `drizzle()` の代わりに呼び出す。

```ts:db/index.ts
import { drizzle, type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'
import Database from 'better-sqlite3'
import * as schema from './schema'
import process from 'node:process'

process.loadEnvFile()

const sqlite = new Database(process.env.DATABASE_URL!)
export const db: BetterSQLite3Database<typeof schema> = drizzle(sqlite, {
  schema,
  casing: 'snake_case',
})
```

:::note warn
こちらでも `casing: 'snake_case'` を付与する必要がある。
:::

次に、リポジトリにユーザーを全件取得するコードを書く。
書き味は SQL そっくり。

https://orm.drizzle.team/docs/select

```ts:lib/repository.tsx
'use server'

import { db } from '@/db'
import { users } from '@/db/schema'
import { asc } from 'drizzle-orm'
import { drizzle } from 'drizzle-orm/better-sqlite3'

export const getUsers = async () => {
  const result = await db
    .select()
    .from(users)
    .orderBy(asc(users.id))

  return result
}
```

ページを読み込んだ際に `console.log` に出力する。

```ts:app/page.tsx
import { getUsers } from '@/lib/repository'

useEffect(() => {
  getUsers().then(res => console.log(res))
}, [])
```

取れたぁ。
`Date` 型への変換も勝手にやってくれるみたい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833190/e318c0e0-c2cb-40bd-8f9a-ffb644794ed6.png)