White Box技術部

WEB開発のあれこれ(と何か)

【TypeScript】Prisma v7でNext.jsのモノレポ構成をYarn v4で実現する方法

先日ヨドバシのクレカ明細を分析するツールを作ったときにPrismaを5系から7系に上げたのですが、 公式のマイグレーションガイドだけでは解決しない点がいくつかあったので、メモを残しておきます。

構成

今回話題に上げるアプリケーション構成は、TypeScriptを利用して、フルスタックNext.jsをyarnでモノレポ管理しているものになります。 またDBはPostgreSQLです。

/
├── apps/
│   └── web/       Next.jsアプリケーション
└── packages/
    └── db/        Prisma管理ディレクトリ

Prisma v5の時点では、packages/db配下でPrismaのクライアントファイルを生成すればnode_modulesに配置されるので、web側では何もしなくてもPrismaClientをimportすることができていました。

以前はnext.config.jsにtranspilePackages: ["@sample-app/db"],のように書いていましたが、 ホスティングされているからかyarn v4で管理している状態では書かなくても利用することができていました。 https://nextjs.org/docs/app/api-reference/config/next-config-js/transpilePackages

今回、この構成はなるべく維持しつつ、v7に上げていきます。

関連しそうなパッケージとバージョンは以下の通りです。

"yarn": "4.12.0"
"next": "16.1.6"
"@prisma/adapter-pg": "7.4.0"
"@prisma/client": "7.4.0"
"dotenv": "17.3.1"
"pg": "8.18.0"
"prisma": "7.4.0"
"pglite-prisma-adapter": "0.7.2"
"tsx": "4.21.0"
"typescript": "5.9.3"

packages/db側の変更点

Prisma関連のファイルを管理するパッケージをpackages/dbに配置しているのですが、変更が必要になったファイルは以下の通りです。

変更が必要なファイル

  • package.json
  • schema.prisma
  • seed.ts
  • .gitignore <- 忘れないように注意

新規追加のファイル

  • prisma.config.ts

package.json関連の話

TypeScriptで記載されたSeedファイルを実行するため、v7以前ではpackage.jsonに以下のような記載をれていましたが、これが不要になりました。というか、あるとエラーになります。

"prisma": {
  "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
},

またこの構成の場合は、以下のパッケージが新たに必要な構成になっているのでdependenciesに追加します。

  • @prisma/adapter-pg
  • dotenv
  • pg
  • tsx

schema.prismaの変更点とprisma.config.tsの追加

v7の大きい変更点は生成されるクライアントファイルの管理が必要になったところだと思います。

冒頭で記載したように以前はnode_modulesに配置されていたものが、リポジトリ管理ファイルとして保持するものに変更されています。 そのためschema.prismaに以下のような変更が必要になります。

  • Before
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["fullTextSearch"]
}
  • After
datasource db {
  provider = "postgresql"
}

generator client {
  provider               = "prisma-client"
  output                 = "./generated/prisma"
  moduleFormat           = "esm"
  generatedFileExtension = "ts"
  importFileExtension    = "ts"
  previewFeatures        = ["fullTextSearchPostgres"]
}

ちなみにgeneratedFileExtensionやimportFileExtensionあたりの設定がないと、TypeScript環境では実行時にファイルが見つからない系エラーが発生します。

そしてprisma.config.tsファイルを新規追加します。

import 'dotenv/config';
import path from 'node:path';
import { defineConfig, env } from 'prisma/config';

export default defineConfig({
  schema: path.join(__dirname, 'prisma/schema.prisma'),
  migrations: {
    path: path.join(__dirname, 'prisma/migrations'),
    seed: 'tsx prisma/seed.ts',
  },
  datasource: {
    url: env('DATABASE_URL'),
  },
});

モノレポ構成でのポイントは、「パスの指定に__dirnameを使う必要があるが、seedコマンドは相対パスで書く」ところです。

seed.tsの変更点

seed.tsも以前は事前準備が、以下のように2行で済むお手軽構成でしたが、

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

v7からはadapterの定義が必要になっています。
例えばカテゴリデータを入れるようなコードは以下のようになります。

import { PrismaClient } from "./generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import { CATEGORIES } from "./seeds/categories";
import "dotenv/config";

const adapter = new PrismaPg({
  connectionString: process.env.DATABASE_URL,
});

const prisma = new PrismaClient({
  adapter,
});

async function main() {
  console.log("Seeding database...");

  for (const category of CATEGORIES) {
    await prisma.category.upsert({
      where: { name: category.name },
      update: {},
      create: category,
    });
  }
  console.log(`Created ${CATEGORIES.length} categories`);

  console.log("Seeding completed.");
}

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

tsconfig.jsonは変更不要

ちなみにtsconfig.jsonは変更が不要でした。今使っている全量はこちらです。

{
  "compilerOptions": {
    "target": "es6",
    "lib": ["dom", "dom.iterable", "es6", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "strictNullChecks": true,
    "forceConsistentCasingInFileNames": true,
    "types": ["node"],
    "typeRoots": ["./node_modules/@types", "../../node_modules/@types"],
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "paths": { "@/*": ["./*"] },
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "incremental": true,
    "sourceMap": true,
    "declaration": true,
    "emitDeclarationOnly": true,
    "outDir": "dist"
  },
  "include": ["prisma/**/*.ts"],
  "exclude": ["node_modules"]
}

apps/web側の変更点

Next.jsアプリケーション側の変更点は

  • PrismaClientのimport変更
  • dependenciesに"@sample-app/db: "*",を追加

になります。

Prisma管理パッケージの名前が"@sample-app/db"で、ファイルの生成先がprisma/generated/prismaとした場合、package.jsonのdependenciesに"@sample-app/db: "*",を追加し、PrismaClientのインスタンスを生成しているファイルでimport先を以下のように変更してください。

import { PrismaClient } from "@sample-app/db/prisma/generated/prisma/client";

運用上の注意

Prismaパッケージのバージョンを上げると、古い生成コードが使えなくな(る場合があ)ります。

✓ Starting...
✓ Ready in 436ms
⨯ TypeError: Cannot read properties of undefined (reading 'graph')
    at createPrismaClient (src/lib/prisma.ts:27:10)
    at <unknown> (src/lib/prisma.ts:30:42)
  25 |   });
  26 |
> 27 |   return new PrismaClient({ adapter });
     |          ^
  28 | };
  29 |
  30 | const prisma = globalForPrisma.prisma || createPrismaClient(); {
  clientVersion: '7.3.0'
}

なので私みたいにしれっとリポジトリ管理してしまわず、.gitignoreにクライアントが生成されるディレクトリを追加しておきましょう。。。

いやでもこれは、マイグレーションガイドの方にも書いておいてくれても!良かったと思う!よ!なんて。