Drizzleの使い方：備忘録

## はじめに
今回、新しくWebアプリケーションを作成しようと思ったんですが、新しいデータベースとしてDrizzleが話題になっていたので、今回使ってみようと思いました（若干はやりに遅れてる感はありますが笑）
今回は以下のドキュメントを参考に立ち上げを行いました。
https://orm.drizzle.team/docs/get-started/neon-new

## 使い方
### 1 必要パッケージのインストール
```shell
bun add drizzle-orm @neondatabase/serverless dotenv
bun add -D drizzle-kit tsx
```

### 2 Neonの設定
Neonのサイト：https://neon.tech/docs/introduction
Neonのサイトに行ってプロジェクトを立ち上げURLを発行
発行したURLは以下のように環境変数として設定
```
DATABASE_URL=
```

### 3 DrizzleのORMをデータベースに接続
以下のディレクトリ構造を作成
```
📦 <project root>
 ├ 📂 drizzle
 ├ 📂 src
 │   ├ 📂 db
 │   │  └ 📜 schema.ts
 │   └ 📜 index.ts
 ├ 📜 .env
 ├ 📜 drizzle.config.ts
 ├ 📜 package.json
 └ 📜 tsconfig.json

```

index.tsに以下のコードを貼り付ける
```typescript
import { drizzle } from 'drizzle-orm/neon-http';

const db = drizzle(process.env.DATABASE_URL);

```

### 4 テーブルを作成
今回はテスト的に、他のサイトから取ってきた以下のschemaをschema.tsに記載
```typescript
import { relations } from "drizzle-orm";
import {
  index,
  integer,
  pgTable,
  primaryKey,
  text,
  timestamp,
} from "drizzle-orm/pg-core";
import type { AdapterAccount } from "next-auth/adapters";

export const users = pgTable("users", {
  id: text("id")
    .primaryKey()
    .$defaultFn(() => crypto.randomUUID()),
  name: text("name"),
  email: text("email").unique(),
  emailVerified: timestamp("emailVerified", { mode: "date" }),
  hashedPassword: text("hashedPassword"),
  image: text("image"),
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at")
    .defaultNow()
    .$onUpdate(() => new Date()),
});

export const accounts = pgTable(
  "account",
  {
    userId: text("userId")
      .notNull()
      .references(() => users.id, { onDelete: "cascade" }),
    type: text("type").$type<AdapterAccount>().notNull(),
    provider: text("provider").notNull(),
    providerAccountId: text("providerAccountId").notNull(),
    refresh_token: text("refresh_token"),
    access_token: text("access_token"),
    expires_at: integer("expires_at"),
    token_type: text("token_type"),
    scope: text("scope"),
    id_token: text("id_token"),
    session_state: text("session_state"),
  },
  (account) => ({
    compoundKey: primaryKey({
      columns: [account.provider, account.providerAccountId],
    }),
  })
);

export const sessions = pgTable("sessions", {
  sessionToken: text("session_token").primaryKey(),
  userId: text("user_id")
    .notNull()
    .references(() => users.id, { onDelete: "cascade" }),
  expires: timestamp("expires", { mode: "date" }).notNull(),
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at")
    .defaultNow()
    .$onUpdate(() => new Date()),
});

export const userRelations = relations(users, ({ many }) => ({
  posts: many(posts),
  likes: many(likes),
}));

export const posts = pgTable(
  "posts",
  {
    id: text("id")
      .primaryKey()
      .$defaultFn(() => crypto.randomUUID()),
    content: text("content").notNull(),
    authorId: text("author_id")
      .notNull()
      .references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" }),
    createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
    updatedAt: timestamp("updated_at", { mode: "date" })
      .defaultNow()
      .notNull()
      .$onUpdate(() => new Date()),
  },
  (posts) => ({
    idIdx: index("posts_id_idx").on(posts.id),
    authorIdx: index("posts_author_idx").on(posts.authorId),
    createdAtIdx: index("posts_created_at_idx").on(posts.createdAt),
  })
);

export const postRelations = relations(posts, ({ one, many }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
  likes: many(likes),
}));

export const likes = pgTable(
  "likes",
  {
    postId: text("post_id")
      .notNull()
      .references(() => posts.id, { onDelete: "cascade", onUpdate: "cascade" }),
    userId: text("user_id")
      .notNull()
      .references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" }),
    createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
    updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull(),
  },
  (like) => ({
    postUserUnique: index("likes_post_user_unique").on(
      like.postId,
      like.userId
    ),
    postIdx: index("likes_post_idx").on(like.postId),
    userIdx: index("likes_user_idx").on(like.userId),
    createdAtIdx: index("likes_created_at_idx").on(like.createdAt),
  })
);

export const likeRelations = relations(likes, ({ one }) => ({
  post: one(posts, {
    fields: [likes.postId],
    references: [posts.id],
  }),
  user: one(users, {
    fields: [likes.userId],
    references: [users.id],
  }),
}));

```

### 5 Drizzleのconfig fileを作成
drizzle.config.tsに以下を記載

```typescript
import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  out: './drizzle',
  schema: './src/db/schema.ts',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

```

### 6 データベースに変更を反映
```shell
bunx drizzle-kit generate
bunx drizzle-kit migrate
```

##　最後に
seedとqueryについてはおいおいやります。
とりあえず実装しやすかったです。