💎

モノレポにおけるback/front間のPrismaの型共有の方法

2022/05/12に公開
3

詳しい方いたら教えてください。めっちゃ欲しい情報ですん。
別にモノレポでなくてもいいんですが、backend/frontendをTSで開発されてる場合Prisma入れてる気がするのですがそういう時の型共有の方法、ggってもあまり出てこない気がする。

Prisma とは

Node.jsのORMです。かなり使いやすくて気に入ってます。

https://www.prisma.io/

スターもたくさんついてますね。

https://github.com/prisma/prisma

お金もたくさん調達できてるみたいでいい感じです。

https://www.prisma.io/blog/series-b-announcement-v8t12ksi6x

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しています。超無駄。

今現在どのように型共有を行っているか

  1. ルート階層にhuskyを置く。
  2. pre-commit段階でbackendのschema.prismaの変更を検知
  3. 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の内訳としては、

  1. まずreset.prismaというファイルを用意し、db:resetで既存のモデルを全て破壊
  2. その後、db:createでschema.prismaをDBに反映させる
  3. 次にnpx prisma generate実行でschema.prismaに合わせて型生成を行う
  4. cpコマンドでbackendのschema.prismaをfrontend内にコピる
  5. frontでもnpx prisma generateを実行

以上の手順で動いてはいる

動きはするし、やりたいことは満たせてるんですが、backendとfrontendで2つPrismaを入れなきゃいけない & front側では型としてしか使ってないので「う〜ん」と。front側を@next/bundle-analyzerでバンドルサイズ測っても、Prismaがかなり大きな部分占めてるんですよね。

backend/node_modules/.prisma/client/index.d.tsをコピるってのも試そうとしたんですが、なんかダメだった気がする。確か同階層の他のモジュールimportしまくってるから結局ディレクトリごとコピらないと意味ない的なだったような。

追記: outputというオプションもある

https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/generating-prisma-client#using-a-custom-output-path

// 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デフォルトの使い方から外れることになります。

https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/generating-prisma-client#why-is-prisma-client-generated-into-node_modulesprismaclient-by-default

また、outputも配列で受け付けてくれるわけではなく、generate時にいっぺんにbackendとfrontendに配置できるわけではないため、ルートに配置したtypesディレクトリをbackend、frontendに複製するシェルを書く必要などもありそう。

というわけで色々足掻いてるので何か良い方法あったら教えてくださいな。

Discussion

aiji42aiji42

個人的には、databaseという名でワークスペースを追加し、そこから@prisma/clientをエクスポートして、backend/frontendからは依存パッケージとしてインポートするという方法を取っています。

そうすることで、@prisma/clientのバージョンもバックとフロントでかんたんに揃えられてよいかと。

ちなみに、ソースはturborepoのexampleです。
(turborepoを採用していなくてもプロジェクトの構造自体は真似れるかと。)
https://github.com/vercel/turborepo/tree/main/examples/with-prisma

takky94takky94

なるほど!!!!
自分じゃ思いつかない方法だったので目から鱗です……ありがとうございます!!!

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

とても勉強になりました、素晴らしい記事をありがとうございます。

先ほどPrismaスキーマを複数のプロジェクトで共有する方法という記事を投稿し、モノレポではない場合にGitのサブモジュール機能を使ってPrismaスキーマを複数のプロジェクトで共有する方法についてまとめたのですが、おわりにの部分でtakky94さんのこちらの記事へのリンクを記載させていただきましたのでお知らせいたします。

モノレポの場合はtakky94さんの記事やaiji42さんのコメントで紹介されている方法を使わせてもらおうと思います。