Closed1
Next.js, Prisma でアプリ作る時のseedデータ作成にこまった
前提
- TypeScript, Next.js (AppRouter), Prisma でプロダクトを作ってる時の話
- PostgreSQL をデータベースとして使用しており、prisma migrate コマンドでマイグレーションをかける想定
結論
- 以下の記事を参考にし tsconfig.local.json を作成する方法を使った
- package.json の module を commonjs に書き換えて、的な話を書いている記事は多いが、Next.js は esnext を推奨しているため、闇雲に tsconfig.local.json を書き換えない方が良い
- seed データを作成する時のみ、
--project tsconig.local.json
で config を渡すようにする
詳細
事前に ts-node
をインストールしておく必要があります
pnpm add -D ts-node
最終的に、ディレクトリはこう言う感じ。
app/
...
prisma/
migrations/
seeds/
0_users.ts # prisma 使ってデータ作る処理
1_books.ts
main.ts # seed データを生成する処理
schema.prisma
tsconfig.json # 通常使うもの
tsconfig.local.json # シードデータ作成時のみ使うもの
Makefile # タスクランナー(と言うテイ)
package.json
pnpm-lock.yaml
tsconfig.local.json はご希望のものがあれば0から作っても良いが、特にこだわりがなければ
cp tsconfig.json tsconfig.local.json
としてしまえば良い。そうした後に
tsconfig.local.json
{
"compilerOptions": {
...,
- "module": "nextes",
+ "module": "commonjs",
}
}
package.json にはこういう感じで書く
package.json
{
"name": "test",
"private": true,
"version": "0.1.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
+ "prisma": {
+ "seed": "ts-node --project tsconfig.local.json prisma/seeds/main.ts"
+ }
}
0_users.ts
の中身はこう言う感じで書いてます。${number}_${table}
としてるのは、テーブルを作成した順番の方がアルファベットよりも意識を向けることが多いためそうしてます。migrations とだいたい気持ちは一緒です
0_users.ts
import type { PrismaClient } from "@prisma/client";
const email = ""; // メールアドレス
const seedUsers = async (prisma: PrismaClient) => {
const start = Date.now();
console.log("Seeding users...");
const user = await prisma.user.upsert({
where: {
email,
},
update: {},
create: {
email,
name: "Test User",
},
});
const end = Date.now();
console.log(`Seeding users completed in ${end - start}ms`);
return user;
};
export default seedUsers;
main.ts
でこう言う感じで使う。同じ階層に書いとくことで、import できない云々の問題を避けれるようにした
main.ts
import { PrismaClient } from "@prisma/client";
import seedUsers from "./0_users";
import seedBooks from "./1_books";
const prisma = new PrismaClient();
async function main() {
const start = new Date();
console.log("Seeding database...");
const user = await seedUsers(prisma);
const books = await seedBooks(prisma, user.id);
const end = new Date();
console.log(`Seeding completed: ${end.getTime() - start.getTime()}ms`);
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
実際は、テーブルが複数あるので依存関係に応じて簡素なパイプラインを組んでます。DAG のライブラリとか、Pipeline ツールを使っても良いです。ただ、そこまでしなくても Promise でそれなりにできる(と思った)
例えば、(言語違いますが)Python の Apache Airflow の記法で疑似コードを書くとこんなイメージのデータがあるとして、簡素なパイプラインは作成処理はこういう感じになります
users >> books
(users, books) >> X
(X, users) >> XX
(X, users) >> XXX
books >> Y
(users, Y) >> YY
(users, Y) >> YYY
const user = await seedUsers(prisma);
const books = await seedBooks(prisma, user.id);
await Promise.all([
seedX(prisma, user.id, books[0].id).then(async (x) => {
await Promise.all([
seedXX(prisma, user.id, x.id);
seedXXX(prisma, user.id, x.id);
]);
},
seedY(prisma, books[0].id).then(async (y) => {
await Promise.all([
seedYY(prisma, user.id, y.id);
seedYYY(prisma, user.id, y.id);
]);
})
]);
で、お好みのタスクランナーで Prisma の実行タイミングでデータベースの情報を引き渡してテストデータを生成してください。私の場合は、Makefile に以下のような定義をして make prisma-seed-dev
としています。(ツール選定に理由はありません。ただ慣れているからだけ)
prisma-seed-dev: ## execute prisma db seed
dotenv -e .env -- npx prisma db seed
このスクラップは2ヶ月前にクローズされました