prisma を勉強したときのまとめ(個人備忘録)

# はじめに
いきなり prisma を学習することになりました
ORM とか全く知らないのに
いろんな記事読み漁って自分で技術検証できた部分をまとめるよ

# ORM を調べた未来の自分より
デメリット（複雑すぎるクエリはしんどい）と
メリット（SQLインジェクション対策や型安全）があるけど
そんな大規模なプロジェクトじゃない限りSQL直書きでもいいらしい
あと、typescript 100% のWEBアプリケーション作るなら、typeORM のほうが良いってよ

# 参考文献
[Node.js(Express.js)環境でPrisma ORMを使いこなすための基礎](https://reffect.co.jp/node-js/prisma-basic) 一部old
[node.js / Prisma ORM / mysql を試す例](https://zenn.dev/knaka0209/articles/0cb8d5ca97bbfc)
[Prisma Migrationの動作を確認してみた](https://dev.classmethod.jp/articles/prisma-migration-preview-confirmation/)
[prismaのschemaファイルの書き方（MySQL用）](https://seri.hatenablog.com/entry/2021/07/27/123755)
[マイグレーション｜Prismaチュートリアル](https://zenn.dev/thirosue/books/49a4ee418743ed/viewer/57d161)
[[Prisma] チートシート - Qiita](https://qiita.com/koffee0522/items/92be1826f1a150bfe62e)
[prisma 入門 2022](https://zenn.dev/takuyanagai0213/articles/4cd72bc30c594d)


# 概要
Prismaとは、Node.js/TypeScript環境で利用できるオープンソースのORMである。

ORM(Object Relational Mapping)とは、オブジェクトとデータベースをマッピングするための技術である。

つまりPrismaとは、データベースを効率的に管理し、簡単に操作を行うためのツールである。

Prismaでは、どのデータベースを利用しているか意識することなく、共通のオブジェクトメソッドを利用し、データベースを操作することができる。

Prismaはアプリケーションを構築する上で必須のツールではなりませんが、データベースの操作に関わるアプリケーション開発のスピードを飛躍的に向上させることができます。

## 機能構成

* Prisma Client
データベースの操作に利用する機能。
```
npx prisma generate
```
schemaを変更した際にズレが生じ、npm run dev した際に毎回エラーが出るので`package.json`に記述しておくと良い。
```
 "scripts": {
    "dev": "ts-node ./script.ts",
    "prisma": "prisma generate"
  },
```
* Prisma Migrate
Prismaの設定ファイル(schema.prisma)にデータモデルを記述しマイグレーションを行うことでデータベースにテーブルを作成することができます。
schema を作成した後に実行する。
* Prisma Studio
```
npx prisma studio
```
テーブルをブラウザ上で閲覧するためのUIも提供しておりブラウザ上からテーブル内のデータを編集することができます。モデル間のリレーションの関係もブラウザ上から把握することができます。


# 環境構築

## TypeScript, Prisma, Expressのインストール
```sh
npde -v
npm -v

mkdir nodejs-prisma
cd nodejs-prisma
npm init -y

npm install typescript ts-node @types/node --save-dev
npx tsc --init

npm install express
npm install @types/express --save-dev # TypesScript用にExpress.jsの型定義をインストール

npm install prisma --save-dev
npx prisma # Prismaで利用できるコマンドのオプションを確認
npx prisma init # プロジェクトフォルダにprismaフォルダと.envファイルが作成される
# npx prisma init --datasource-provider sqlite # DBの設定変更（デフォルトはpostgresql）
# npx prisma init --datasource-provider mysql # DBの設定変更（デフォルトはpostgresql）
```
* prismaフォルダ内：設定ファイルであるschema.prismaファイル
* .envファイル：データベースに接続するために必要となる環境変数を設定する


## DBの設定
`schema.prisma` ファイルと `.env` ファイル内を変更
DB作成権限がないとエラーになる

```:.env
# mysql://ユーザー名:パスワード@localhost:3306/DB名
DATABASE_URL="mysql://root:prismatest@localhost:3306/testd02"
```

potsテーブルを作成するためにデータモデルの定義(Data model Definition)を記述する
```prisma:schema.prisma
datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model pot {
  id      Int      @id @default(autoincrement())
  name    String
  score   Int?    @unique
  email   String  @@map('e_mail')

  @@map('pots')
}
```

* **@id**：主キー
* **@default()**：デフォルト値セット
* **autoincrement()**：自動採番
* **?**：null許容
* **@unique**：カラムで一意の値
* **@@unique([column1, column2])**：複合キー
* **@@map('e-mail')**：独自の名前をマッピングできる

[Prisma Schemaの書き方あれこれ](https://zenn.dev/ikekyo/scraps/f6c87fbfd3bf9d)
[Prisma schema reference](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference)

|types|types_schema|type attribute|
|-|-|-|
|varchar|String|@db.VarChar(X)|
|char|String|@db.Char(X)|
|TINYINT(1)|Boolean|@db.TinyInt(1)|
|BIT(1)|Boolean|@db.Bit(1)|
|datetime|DateTime|@db.DateTime()|
|date|DateTime|@db.Date()|
|time|DateTime|@db.Time()|
|timestamp|DateTime|@db.Timestamp()|


## マイグレーションの実行
shema.prisma に記述したモデルを元にデータベースにテーブルを作成すること
マイグレーションに任意の名前をつける必要がある
```sh
npx prisma migrate dev --name my_migrate_init
```
migration フォルダとその中に `migration.sql` ファイルが生成される。


### Prisma Studio でテーブルへのアクセス
```sh
npx prisma studio
```
ブラウザで `localhost:5555` にアクセス


### マイグレーションのリセット
テーブルを一度削除し再生成します。
```sh
npx prisma migrate reset
```

### マイグレーションのpushとpull
```sh
# schema.prisma ファイルのモデルを変更した際に、migration ファイルを生成せずにテーブルを変更する。
npx prisma db push

# 現在のDBの状態を schema.prisma ファイルに反映させる。
npx prisma db pull
```

### マイグレーションによるSeeding
Seeding ファイルを利用すると、マイグレーションの実行時にテーブルにデータを挿入できる。
prisma フォルダに seed.ts ファイルを作成しデータをテーブルに追加する処理を記述。
なんかよくわからん。諦🍈 → [here](#prismaseedts-feat-relations)


## Express.js で Prisma のメソッドを利用

TSとJSが混在しています。

users テーブルを操作する関数。

### findMany：全データを取得する
```js
const prisma = new PrismaClient();

app.get('/users', async (req: Request, res: Response) => {
  const users = await prisma.users.findMany();
  return res.json(users);
});
```

### create：データの登録
```js
app.post('/users', async (req: Request, res: Response) => {
  const { name, email } = req.body;
  try {
    const user = await prisma.user.create({
      data: {
        name,
        email,
      },
    });
    return res.json(user);
  } catch (e) {
    if (e instanceof Prisma.PrismaClientKnownRequestError) {
      if (e.code === 'P2002') {
        console.log(
          'There is a unique constraint violation, a new user cannot be created with this email'
        );
      }
    }
    return res.status(400).json(e);
  }
});
```

### update：データの更新
```js
app.put('/users/:id', async (req: Request, res: Response) => {
  const id = Number(req.params.id);
  const { name } = req.body;
  try {
    const user = await prisma.users.update({
      where: {
        id,
      },
      data: {
        name,
      },
    });
    return res.json(user);
  } catch (e) {
    return res.status(400).json(e);
  }
});
```

### delete：データの削除
```js
app.delete('/users/:id', async (req: Request, res: Response) => {
  const id = Number(req.params.id);
  try {
    const user = await prisma.users.delete({
      where: {
        id,
      },
    });
    return res.json(user);
  } catch (e) {
    return res.status(400).json(e);
  }
});
```

### findUnique：特定フィールドの取得
```js
app.get('/users/:id', async (req: Request, res: Response) => {
  const id = Number(req.params.id);
  try {
    const user = await prisma.users.findUnique({
      where: {
        id,
      },
    });
    return res.json(user);
  } catch (e) {
    return res.status(400).json(e);
    console.log('IDが存在しません。')
  }
});
```

### findFirst：最後に追加されたフィールドの取得
```js
const user = await prisma.users.findFirst({
  where: { name: 'Alice' },
})
```

### upsert：指定したIDが存在する場合update、存在しない場合createを実行
```js
const user = await prisma.users.upsert({
  where: { id: 1 },
  create: {
    email: 'alice@prisma.io',
    name: 'Alice',
  },
  update: {
    email: 'alice@prisma.io',
    name: 'AliceB',
  },
})
```

### createMany：一度に複数のフィールドの挿入
```js
const users = await prisma.users.createMany({
  data: [
    { name: 'Sonali', email: 'sonali@prisma.io' },
    { name: 'Alex', email: 'alex@prisma.io' },
  ],
})
```

### updateMany：一度に複数のフィールドを更新
### deleteMany：一度に複数のフィールドを削除


## リレーションの設定

[link](https://zenn.dev/tsucchiiinoko/articles/f222dbbfa23325)
[refarence](https://www.prisma.io/docs/concepts/components/prisma-schema/relations)

schema.prisma ファイルに追加記述
* 1対Nの場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3077025/83d65317-5b0d-cd77-d963-681ab35d47b7.png)


```prisma:schema.prisma
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  Posts Post[]
}

model Post {
  id       Int  @id @default(autoincrement())
  author   User @relation(fields: [authorId], references: [id])
  authorId Int
}
```
もしくはマッチングキーを利用
```prisma:schema.prisma
model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  Posts Post[]  @relation("author")　
}

model Post {
  id       Int  @id @default(autoincrement())
  author   User @relation("author", fields: [authorId], references: [id])
  authorId Int
}
```
下記の sql と同等
```sql
-- テーブルの外部キー制約
-- 1対NのNの方のテーブルにキー制約をかける。
ALTER TABLE `Post`
  ADD CONSTRAINT `author` FOREIGN KEY (`authorId`) REFERENCES `User` (`id`);
```

#### 複数カラムが同じテーブルにつながっている場合
リレーション名を設定して一意にする。
```prisma:schema.prisma
model videos {
  V_id         Int    @id @default(autoincrement())
  V_title      String @db.VarChar(255)

  V_VP_a video_pairs[] @relation("a")
  V_VP_b video_pairs[] @relation("b")
}

model video_pairs {
  VP_id         Int @id @default(autoincrement())
  VP_a_video_id Int
  VP_b_video_id Int

  VP_V_a videos @relation(fields:[VP_a_video_id], references:[V_id], name: "a")
  VP_V_b videos @relation(fields:[VP_b_video_id], references:[V_id], name: "b")
}
```


## VIEW or JOIN
Migrate を使用してスキーマ内のビューをデータベースに適用することはまだできません
手動でなんかいろいろせにゃならんでめんどくさいみたいです。

[prisma.jsで覚えたメモ](https://px-wing.hatenablog.com/entry/2021/04/02/065619)
[How to include views in your Prisma schema](https://www.prisma.io/docs/concepts/components/prisma-schema/views)
[PrismaのJOINの挙動を観察](https://qiita.com/masayasviel/items/5fa9744ac45d846903a9)
~~[How to create a prisma model from a SQL view - Stack Overflow](https://stackoverflow.com/questions/68100340/how-to-create-a-prisma-model-from-a-sql-view)~~
[データベース ビューのサポートを追加 GitHub](https://github.com/prisma/prisma/issues/678)
[PrismaからRDBのViewを参照する](https://tech-karadanote.hateblo.jp/entry/2021/04/16/135244)

DBに別の方法でViewを作成し schema を記述すると参照できる。

```ts
import { PrismaClient } from '@prisma/client';

(async () => {
  const prisma = new PrismaClient({
    log: ['query'],
  });
  const params = await prisma.user.findMany({
    select: {
      id: true,
      name: true,
      UserConfig: {
        select: {
          category: true
        }
      }
    },
    where: {
      UserConfig: {
        category: {
          in: ['01', '02']
        }
      }
    }
  });
  console.log(params);
})();
```


## prisma/seed.ts feat. Relations
[PrismaでSeedでデータを投入する](https://m-shige1979.hatenablog.com/entry/2021/11/20/213051)
[prismaのseedを複数ファイルに分割する](https://zenn.dev/mano_r/articles/17df03afc08601)
[Seeding with relations?](https://github.com/prisma/prisma/discussions/11531)
[Relations (Reference)](https://www.prisma.io/docs/concepts/components/prisma-schema/relations)
[Seeding your database](https://www.prisma.io/docs/guides/migrate/seed-database)

1. seed ファイルを用意
複数ファイルに分割したい場合、 seed ディレクトリを作成し、中にファイルを用意
```ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

async function main() {
const alice = await prisma.users.upsert({
   where: { U_id: 1 },
   update: {},
   create: {
      U_name: 'alice@prisma.io',
      U_age: 21,
      U_gender: 0,
   },
});

console.log({ alice });
}

main()
.catch((e) => {
   console.error(e);
   process.exit(1);
})
.finally(async () => {
   await prisma.$disconnect();
});
```
または
```ts
import { PrismaClient, Prisma } from '@prisma/client'
const prisma = new PrismaClient()

// モデル投入用のデータ定義
const userData: Prisma.UserCreateInput[] = [
  {
    name: 'hoge1',
    email: 'hoge1@example.com',
  },
  {
    name: 'hoge2',
    email: 'hoge2@example.com',
  },
  {
    name: 'hoge3',
    email: 'hoge3@example.com',
  },
]

const transfer = async () => {
  const users = [];
  for (const u of userData) {
    const user = prisma.user.create({
      data: u,
    })
    users.push(user);
  }
  return await prisma.$transaction(users);
}

// 定義されたデータを実際のモデルへ登録する処理
const main = async () => {
  console.log(`Start seeding ...`)

  await transfer();

  console.log(`Seeding finished.`)
}

// 処理開始
main()
  .catch((e) => {
    console.error(e)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.$disconnect()
  })
```

2. package.jsonにprismaのseedの設定を加えます。
```json
"prisma": {
  "seed": "ts-node prisma/seed.ts"
}
```
複数ファイルに分割した場合以下のようにする。
```json
"prisma": {
  "seed": "ts-node ./prisma/seed/start.ts"
}
```

3. 実行
```sh
npx prisma db seed
```

# おわりに
他の Prisma 記事です

https://qiita.com/takekota/items/ad7a0b26b042d9bc725a

https://qiita.com/takekota/items/e01b48096435fc01cae7