モノレポにおけるback/front間のPrismaの型共有の方法
詳しい方いたら教えてください。めっちゃ欲しい情報ですん。
別にモノレポでなくてもいいんですが、backend/frontendをTSで開発されてる場合Prisma入れてる気がするのですがそういう時の型共有の方法、ggってもあまり出てこない気がする。
Prisma とは
Node.jsのORMです。かなり使いやすくて気に入ってます。
スターもたくさんついてますね。
お金もたくさん調達できてるみたいでいい感じです。
Prismaの型の生成
参考: Set up Prisma
上記ページをもとにサクッとinstallすると /prisma に schema.prismaというファイルが生成されます。そのファイルに、例えばこんな感じでスキーマを定義してみます。
// ユーザー
model User {
id String @id @default(cuid())
slug String @unique @default(cuid())
name String
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
profile Profile?
status Status?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
定義できたらnpm install @prisma/client && npx prisma generate
を実行するだけ。
すると、 node_modules/.prisma/client/index.d.ts
にこんな感じでTSのtypeを生成してくれます。
/**
* Model User
*
*/
export type User = {
id: string
slug: string
name: string
email: string | null
emailVerified: Date | null
image: string | null
createdAt: Date
updatedAt: Date
}
👆 こいつを共有したいンゴねぇ
すればええやん?
そうなんですけど、自分の今の環境が下記のような構成で(みんなこんなもんだと思うけど)、
.
├── .github
├── README.md
├── backend
├── docker
├── frontend
├── node_modules
├── package-lock.json
└── package.json
backend内とfrontend内とで別々のデプロイ先に行きます。なので、ルート階層にprismaを設置するとbackendやfrontend側からprismaの型を参照できず、ビルドエラーが起こってしまう。そこでやむを得ず、backendとfrontendの両方にprismaをinstallしています。超無駄。
今現在どのように型共有を行っているか
- ルート階層にhuskyを置く。
- pre-commit段階でbackendのschema.prismaの変更を検知
- backendのschema.prismaをfrontendのschema.prismaにコピる
1/2. husky
Prettierとかと併用するイメージのhusky。pre-commit段階で任意のコマンドを実行することができます。細かいinstall方法などは割愛。.husky/pre-commitを下記のように設定しました。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
cd $(dirname "$0")/../
# git addした差分の一覧を変数に格納
changedFiles=$(git diff --diff-filter=d --cached --name-only)
echo $changedFiles
# 差分の一覧にbackendのprisma内のファイルが含まれている場合はnpm run db:migrate実行
if [[ "$changedFiles" == *backend/prisma* ]]; then
cd ./backend && npm run db:migrate
echo "[~/.husky/pre-commit] スキーマ情報の更新あり。フロントのPrismaも更新"
else
echo "[~/.husky/pre-commit] スキーマ情報の更新なし"
fi
3. backendのschema.prismaをfrontendのschema.prismaにコピる
👇 backendのpackage.jsonはこんな感じ。
"scripts": {
"db:edited": "npx prisma generate",
"db:reset": "npx prisma db push --accept-data-loss --schema=./prisma/reset.prisma",
"db:create": "npx prisma db push",
"db:migrate": "npm run db:reset && npm run db:create && npm run db:edited && cp -f ./prisma/schema.prisma ../frontend/prisma/schema.prisma && cd ../frontend && npx prisma generate",
db:migrate
の内訳としては、
- まずreset.prismaというファイルを用意し、
db:reset
で既存のモデルを全て破壊 - その後、
db:create
でschema.prismaをDBに反映させる - 次に
npx prisma generate
実行でschema.prismaに合わせて型生成を行う -
cp
コマンドでbackendのschema.prismaをfrontend内にコピる - frontでも
npx prisma generate
を実行
以上の手順で動いてはいる
動きはするし、やりたいことは満たせてるんですが、backendとfrontendで2つPrismaを入れなきゃいけない & front側では型としてしか使ってないので「う〜ん」と。front側を@next/bundle-analyzerでバンドルサイズ測っても、Prismaがかなり大きな部分占めてるんですよね。
backend/node_modules/.prisma/client/index.d.tsをコピるってのも試そうとしたんですが、なんかダメだった気がする。確か同階層の他のモジュールimportしまくってるから結局ディレクトリごとコピらないと意味ない的なだったような。
追記: outputというオプションもある
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
+ output = "../../types"
}
以上のようにschema.prismaにoutputパスを書いてやればルートに置くことができます。(別にbackend内でもいいですがそれだとbackend/node_modules/.prisma/clientに設置するデフォのやり方と遜色ない)
.
├── README.md
├── backend
├── docker
├── frontend
├── node_modules
├── package-lock.json
├── package.json
+└── types
ただし、これはPrismaデフォルトの使い方から外れることになります。
また、outputも配列で受け付けてくれるわけではなく、generate時にいっぺんにbackendとfrontendに配置できるわけではないため、ルートに配置したtypesディレクトリをbackend、frontendに複製するシェルを書く必要などもありそう。
というわけで色々足掻いてるので何か良い方法あったら教えてくださいな。
Discussion
個人的には、
database
という名でワークスペースを追加し、そこから@prisma/clientをエクスポートして、backend/frontendからは依存パッケージとしてインポートするという方法を取っています。そうすることで、
@prisma/client
のバージョンもバックとフロントでかんたんに揃えられてよいかと。ちなみに、ソースはturborepoのexampleです。
(turborepoを採用していなくてもプロジェクトの構造自体は真似れるかと。)
なるほど!!!!
自分じゃ思いつかない方法だったので目から鱗です……ありがとうございます!!!
とても勉強になりました、素晴らしい記事をありがとうございます。
先ほどPrismaスキーマを複数のプロジェクトで共有する方法という記事を投稿し、モノレポではない場合にGitのサブモジュール機能を使ってPrismaスキーマを複数のプロジェクトで共有する方法についてまとめたのですが、おわりにの部分でtakky94さんのこちらの記事へのリンクを記載させていただきましたのでお知らせいたします。
モノレポの場合はtakky94さんの記事やaiji42さんのコメントで紹介されている方法を使わせてもらおうと思います。