ラベル Prisma の投稿を表示しています。 すべての投稿を表示
ラベル Prisma の投稿を表示しています。 すべての投稿を表示

2025年1月21日火曜日

Prisma で many to many のリレーションシップを定義する

Prisma での many to many のリレーションシップ定義・操作の勉強を行う。

前提

プロジェクトの初期化

npm init

必要パッケージのインストール

npm i dotenv
npm i -D typescript tsx @types/node prisma
  • dependencies
    • dotenv: 接続先設定を env に記述するために利用
  • devDependencies
    • typescript: TypeScript トランスパイルに利用
    • tsx: ts を実行できるコマンド。チュートリアルに入っていたから入れたが、使いどころは謎。
    • @types/node: node の型情報

DB 接続設定の作成

.env ファイルに接続先設定を記述する。

DATABASE_URL=postgres://postgres:postgres@postgres/postgres

TypeScript 設定ファイル(tsconfig.json)の作成

tsconfig.json の作成

tsc コマンドで、 tsconfig.json を作成する

npx tsc --init

tsconfig.json のパラメーター編集

以下表のとおりパラメーターを設定。

パラメーター
outDir ./dist

ここまでの動作確認

ひとまず TypeScript をコンパイル・実行できるかを確認。

mkdir src
echo 'console.log("Hello, World!");' > ./src/index.ts
tsc
node ./dist/index.js

Hello, World! が表示される … ok!

Prisma の設定

Prisma の初期化

npx prisma init --datasource-provider postgresql

prisma/schema.prisma にスキーマファイルが生成される。

NOTE: ここで、 npx prisma db pull とすると、既存 DB を基にスキーマファイルを更新してくれる

DB スキーマの作成

Prisma の DSL でスキーマを書いていく。

model Book {
  id      Int      @id @default(autoincrement())
  title   String
  authors BooksOnAuthors[]
}

model Author {
  id    Int    @id @default(autoincrement())
  name  String
  books BooksOnAuthors[]
}

model BooksOnAuthors {
  id         Int    @id @default(autoincrement())
  // 本を削除する際には、著者へのリレーションシップは消えて欲しいのでカスケード設定
  book       Book   @relation(fields: [bookId], references: [id], onDelete: Cascade, onUpdate: Cascade)
  bookId     Int // リレーションスカラーフィールド。 上記の `@relation``fields` で指定した名前にする。
  // 著者を消そうとしたときに、その著者の本がまだある場合はエラーにして火しいのでカスケードなし
  author     Author @relation(fields: [authorId], references: [id])
  authorId Int // リレーションスカラーフィールド
}

prisma client のインストールとクライアントコード生成

この時点で、モデルに対応した API が生成されるようだ。

npx prisma generate

マイグレーションの実行

npx prisma migrate dev --name init

prisma/migrations にマイグレーション用 SQL が生成され、それが実行される。

A5:SQL-Mk2 で確認してみると、確かにテーブルが作成されている … ok!

メインファイルの作成

作成した Prisma の設定で DB にアクセスし、 CRUD するアプリケーションを作成する。

src/index.ts:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  // Author を生成
  const createdAuthor = await prisma.author.create({
    data: {
      name: 'mikoto2000',
    },
  })
  console.dir(`createdAuthor: ${JSON.stringify(createdAuthor)}`);

  // Book と、 Author へのリレーションシップを生成
  const createdBook = await prisma.book.create({
    data: {
      title: 'mikoto2000\'s history',
      authors: {
        create: [
          {
            authorId: createdAuthor.id
          }
        ]
      }
    },
  })
  console.dir(`createdBook: ${JSON.stringify(createdBook)}`);

  // 作成した Book の検索(中間テーブル・Author を JOIN して、戻り値に含める)
  const foundBook = await prisma.book.findMany({
    include: {
      authors: {
        include: {
          author: {}
        }
      }
    }
  });
  console.dir(`foundBook: ${JSON.stringify(foundBook)}`);
  console.dir(`プロパティ取得の練習: { title: ${foundBook[0].title}, Author: ${foundBook[0].authors.map((e) => e.author.name)}}`);

  // Book を削除(中間テーブルの要素と Book 自体の削除。中間テーブルはカスケード設定をしているので自動で削除される)
  const deletedBook = await prisma.book.delete({
    where: {
      id: createdBook.id
    },
  });
  console.dir(`deletedBook: ${JSON.stringify(deletedBook)}`);

  // Author を削除
  const deletedAuthor = await prisma.author.delete({
    where: {
      id: createdAuthor.id
    }
  });
  console.dir(`deletedAuthor: ${JSON.stringify(deletedAuthor)}`);
}

main()
  .then(async () => {
    await prisma.$disconnect()
  })
  .catch(async (e) => {
    console.error(e)
    await prisma.$disconnect()
    process.exit(1)
  })

動作確認

ビルド

npx tsc

実行

node ➜ ~/tmp $ node ./dist/index.js 
'createdAuthor: {"id":1,"name":"mikoto2000"}'
`createdBook: {"id":1,"title":"mikoto2000's history"}`
`foundBook: [{"id":1,"title":"mikoto2000's history","authors":[{"id":1,"bookId":1,"au
thorId":1,"author":{"id":1,"name":"mikoto2000"}}]}]`
"プロパティ取得の練習: { title: mikoto2000's history, Author: mikoto2000}"
`deletedBook: {"id":1,"title":"mikoto2000's history"}`
'deletedAuthor: {"id":1,"name":"mikoto2000"}'

ok! 以上。

参考資料

2025年1月20日月曜日

Prisma に TypeScript と PostgreSQL で入門する

Drizzle との違いを理解するため Prisma の Get Started をやってみる。

前提

プロジェクトの初期化

npm init

必要パッケージのインストール

npm i dotenv
npm i -D typescript tsx @types/node prisma
  • dependencies
    • dotenv: 接続先設定を env に記述するために利用
  • devDependencies
    • typescript: TypeScript トランスパイルに利用
    • tsx: ts を実行できるコマンド。チュートリアルに入っていたから入れたが、使いどころは謎。
    • @types/node: node の型情報

DB 接続設定の作成

.env ファイルに接続先設定を記述する。

DATABASE_URL=postgres://postgres:postgres@postgres/postgres

TypeScript 設定ファイル(tsconfig.json)の作成

tsconfig.json の作成

tsc コマンドで、 tsconfig.json を作成する

npx tsc --init

tsconfig.json のパラメーター編集

以下表のとおりパラメーターを設定。

パラメーター
outDir ./dist

ここまでの動作確認

ひとまず TypeScript をコンパイル・実行できるかを確認。

mkdir src
echo 'console.log("Hello, World!");' > ./src/index.ts
tsc
node ./dist/index.js

Hello, World! が表示される … ok!

Prisma の設定

Prisma の初期化

npx prisma init --datasource-provider postgresql

prisma/schema.prisma にスキーマファイルが生成される。

NOTE: ここで、 npx prisma db pull とすると、既存 DB を基にスキーマファイルを更新してくれる

DB スキーマの作成

Prisma の DSL でスキーマを書いていく。

以下コードを見れば、どんなスキーマを作りたいのかわかるはず…

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

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User    @relation(fields: [authorId], references: [id])
  authorId  Int
}

prisma client のインストールとクライアントコード生成

この時点で、モデルに対応した API が生成されるようだ。

npx prisma generate

マイグレーションの実行

npx prisma migrate dev --name init

prisma/migrations にマイグレーション用 SQL が生成され、それが実行される。

A5:SQL-Mk2 で確認してみると、確かにテーブルが作成されている … ok!

メインファイルの作成

作成した Prisma の設定で DB にアクセスし、 CRUD するアプリケーションを作成する。

src/index.ts:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  // User を生成
  const createdUser = await prisma.user.create({
    data: {
      name: 'mikoto2000',
      email: 'mikoto2000@gmail.com',
    },
  })
  console.dir(`createdUser: ${JSON.stringify(createdUser)}`);

  // user を検索
  const foundUser = await prisma.user.findUnique({
    where: {
      email: 'mikoto2000@gmail.com',
    }
  });
  console.dir(`foundUser: ${JSON.stringify(foundUser)}`);

  // user を更新
  if (foundUser) {
    const updatedUser = await prisma.user.update({
      where: {
        email: foundUser.email,
      },
      data: {
        name: 'mikoto2000+prisma@gmail.com',
      },
    })
    console.dir(`updatedUser: ${JSON.stringify(updatedUser)}`);
  }

  // user を再び検索
  const foundUser2 = await prisma.user.findMany({
    where: {
      name: 'mikoto2000+prisma@gmail.com',
    }
  });
  console.dir(`foundUser2: ${JSON.stringify(foundUser2)}`);

  // user を削除
  const deletedUser = await prisma.user.delete({
    where: {
      id: createdUser.id
    }
  });
  console.dir(`deletedUser: ${JSON.stringify(deletedUser)}`);
}

main()
  .then(async () => {
    await prisma.$disconnect()
  })
  .catch(async (e) => {
    console.error(e)
    await prisma.$disconnect()
    process.exit(1)
  })

動作確認

ビルド

npx tsc

実行

node ➜ /workspaces $ node ./dist/index.js
'createdUser: {"id":21,"email":"mikoto2000@gmail.com","name":"mikoto2000"}'
'foundUser: {"id":21,"email":"mikoto2000@gmail.com","name":"mikoto2000"}'
'updatedUser: {"id":21,"email":"mikoto2000@gmail.com","name":"mikoto2000+prisma@gmail.com"}'
'foundUser2: [{"id":21,"email":"mikoto2000@gmail.com","name":"mikoto2000+prisma@gmail.com"}]'
'deletedUser: {"id":21,"email":"mikoto2000@gmail.com","name":"mikoto2000+prisma@gmail.com"}'

ok! 以上。

参考資料