TypeScriptでORMのPrismaに入門した

### はじめに

PrismaはNode.jsとTypeScript用のORMです。
次のツール群で構成されています。

- Prisma Client
- Prisma Migrate
- Prisma Studio

https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch-typescript-postgres に記載されている手順で、プロジェクト作成を作成しPrismaを触ってみました。

※ Prisma Studioの部分に関しては、試していないので記載していません。

### 実行環境

|                | バージョン |
| :-----------   | :-------- |
| npm            | 6.14.7    |
| node           | v14.7.0   |
| prisma         | 2.25.0    |
| @prisma/client | 2.25.0    |

### プロジェクト作成

```sh
$ mkdir hello-prisma 
$ cd hello-prisma
$ npm init -y
$ npm install prisma typescript ts-node @types/node --save-dev
```

`tsconfig.json`を作成して、以下の内容を記載します。

```json:tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "lib": ["esnext"],
    "esModuleInterop": true
  }
}
```

以下のコマンドを実行すると、`.env`ファイルと`prisma/schema.prisma`が作成されます。

```sh
$ npx prisma init
```

### データベースの準備
データベースはPostgreSQLを使用するので、Dockerを使ってPostgreSQLの準備をします。
（ここは公式サイトには記載がない内容です）
`docker-compose.yml`を作成し、`docker compose up -d`を実行する。

```yml:docker-compose.yml
version: "3"
services:
  db:
    image: postgres:13.3
    environment:
      POSTGRES_USER: sample
      POSTGRES_PASSWORD: password
    ports:
      - 5432:5432
    volumes:
      - postgres:/var/lib/postgresql
volumes:
  postgres:
```

### データベースに接続する

PostgreSQLを使用するので、自動生成された`prisma/schema.prisma`のままで変更はありません。

```prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}
```

データベースへの接続URLは`.env`ファイルから取得しているので、Dockerで起動したPostgreSQLに接続できるように変更します。
URLは`postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA`の形式で設定します。
`USER`と`PASSWORD`は、`docker-compose.yml`で指定した`POSTGRES_USER`と`POSTGRES_PASSWORD`の値になります。

```:.env
DATABASE_URL="postgresql://sample:password@localhost:5432/mydb?schema=public"
```

### データベースとテーブルの作成

`prisma/schema.prisma`に作成するテーブルの定義を追記し、migrateを実行します。

```prisma/schema.prisma
model Post {
  id        Int      @default(autoincrement()) @id
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title     String   @db.VarChar(255)
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

model Profile {
  id     Int     @default(autoincrement()) @id
  bio    String?
  user   User    @relation(fields: [userId], references: [id])
  userId Int     @unique
}

model User {
  id      Int      @default(autoincrement()) @id
  email   String   @unique
  name    String?
  posts   Post[]
  profile Profile?
}
```

```sh
$ npx prisma migrate dev --name init
```

`prisma/migrations/20210622135843_init/migration.sql`が作成されます。

### Prisma Clientをインストール

```sh
$ npm install @prisma/client
```

### クエリの実行

Prisma Clientを生成したので、スクリプトからデータベースに対してクエリを実行します。

```ts:index.ts
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  const allUsers = await prisma.user.findMany()
  console.log(allUsers)
}

main()
  .catch((e) => {
    throw e
  })
  .finally(async () => {
    await prisma.$disconnect()
  })
```

`index.ts`を実行してデータベースからデータを取得してみます。

```sh
$ npx ts-node index.ts
[]
```

`User`テーブルの全レコードを取得するクエリを実行していますが、まだ何もデータが登録されていないので`[]`が出力されます。
`User`/`Post`/`Profile`にデータを作成してから、取得クエリを実行します。

```ts:index.ts
async function main() {
  await prisma.user.create({
    data: {
      name: 'Alice',
      email: 'alice@prisma.io',
      posts: {
        create: { title: 'Hello World' },
      },
      profile: {
        create: { bio: 'I like turtles' },
      },
    },
  })

  const allUsers = await prisma.user.findMany({
    include: {
      posts: true,
      profile: true,
    },
  })
  console.dir(allUsers, { depth: null })
}
```

```sh
$ npx ts-node index.ts
[
  {
    id: 1,
    email: 'alice@prisma.io',
    name: 'Alice',
    posts: [
      {
        id: 1,
        createdAt: 2021-06-22T14:15:54.213Z,
        updatedAt: 2021-06-22T14:15:54.214Z,
        title: 'Hello World',
        content: null,
        published: false,
        authorId: 1
      }
    ],
    profile: { id: 1, bio: 'I like turtles', userId: 1 }
  }
]
```

`Post`テーブルの`published`カラムを`true`に更新します。

```ts:index.ts
async function main() {
  const post = await prisma.post.update({
    where: { id: 1 },
    data: { published: true },
  })
  console.log(post)
}
```

```sh
$ npx ts-node index.ts
{
  id: 1,
  createdAt: 2021-06-22T14:15:54.213Z,
  updatedAt: 2021-06-22T14:21:05.080Z,
  title: 'Hello World',
  content: null,
  published: true,
  authorId: 1
}
```

### おまけ：実行されるSQLを確認する

実際にどういうSQLが発行されているのか知りたい場合は、ログ出力すれば確認できます。
`PrismaClient`のインスタンスを生成する際の引数に`{ log: ['query']}`を渡すと、実行されるSQLのログが出力されます。

```diff:index.ts
- const prisma = new PrismaClient()
+ const prisma = new PrismaClient({ log: ['query']})
```

```sh
$ npx ts-node index.ts
prisma:query SELECT "public"."User"."id", "public"."User"."email", "public"."User"."name" FROM "public"."User" WHERE 1=1 OFFSET $1
[ { id: 1, email: 'alice@prisma.io', name: 'Alice' } ]
```

### さいごに

基本的に公式サイトに記載されている通りの手順で進めたので、詰まるところはありませんでした。
今回使われていないPrisma ClientのAPIがまだまだたくさんあるので、色々触っていこうと思います。