Claude Code के साथ Prisma ORM: schema, migration, transaction और test
Claude Code से Prisma ORM: schema, migration review, transactions, seed/test और production checklist.
Prisma ORM TypeScript से DB को सुरक्षित तरीके से इस्तेमाल करने वाली typed database layer है। यहschema.prismaमें data model रखता है, Prisma Migrate से SQL migration बनाता है, और Prisma Client से type-safe queries लिखने देता है। Claude Code के साथ यह workflow तेज हो जाता है, लेकिन database design को बिना review के AI पर छोड़ना ठीक नहीं है।
अगर prompt सिर्फ “Prisma CRUD बना दो” है, तो code चल सकता है पर production-ready नहीं होगा। Index छूट सकते हैं,includeबहुत बड़ा हो सकता है, cascade delete जरूरत से ज्यादा खतरनाक हो सकता है, और migration real data पर fail हो सकती है। इस article में blog API के उदाहरण से दिखाया गया है कि Claude Code से schema, migration review, transactions, seed/test और review checklist कैसे बनवाएं।
Official references के लिए Prisma ORM, Prisma Schema, Transactions, और Prisma Migrate देखें। Related ClaudeCodeLab guides: Claude Code getting started, Drizzle ORM, Supabase integration, और Redis caching।
Workflow
flowchart LR
A["Product rules Claude Code को दें"] --> B["schema.prisma design"]
B --> C["migration.sql generate और review"]
C --> D["Prisma Client queries"]
D --> E["seed और tests"]
E --> F["Production checklist"]
Prisma work को तीन artifacts में बांटें: schema, SQL migration, और TypeScript query code। Claude Code draft बनाए, लेकिन constraints, indexes, delete rules और transaction boundaries को इंसान review करे।
Claude Code prompt
Blog API के लिए Prisma ORM data layer design करें।
Context:
- TypeScript + Prisma ORM + SQLite local, बाद में PostgreSQL migration संभव
- Models: User, Post, Category, Comment, Notification, AuditLog
- Post status string होगा: DRAFT/PUBLISHED/ARCHIVED
- email और slug unique होंगे
- Public listing status, publishedAt, author, category से filter होगी
- Post delete होने पर Comment और join rows cascade delete हों
- User delete अभी Restrict रहे; anonymization अलग migration में बने
Return:
1. prisma/schema.prisma
2. migration SQL review points
3. Prisma Client create/list/publish code
4. seed और test commands
5. production review checklist
Prompt में business rules लिखना जरूरी है। कौन सा data delete हो सकता है, कौन सा नहीं, किस list screen को index चाहिए, और कौन सा operation atomic होना चाहिए, यह स्पष्ट होना चाहिए।
Local setup
{
"type": "module",
"scripts": {
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:deploy": "prisma migrate deploy",
"db:seed": "prisma db seed",
"dev": "tsx src/demo.ts",
"test": "vitest run"
},
"dependencies": {
"@prisma/client": "latest",
"dotenv": "latest"
},
"devDependencies": {
"prisma": "latest",
"tsx": "latest",
"typescript": "latest",
"vitest": "latest"
}
}
npm install
echo 'DATABASE_URL="file:./dev.db"' > .env
mkdir prisma src
// prisma.config.ts
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
seed: "tsx prisma/seed.ts",
},
datasource: {
url: env("DATABASE_URL"),
},
});
schema.prisma
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "sqlite"
}
model User {
id String @id @default(cuid())
email String @unique
name String
role String @default("editor")
posts Post[]
comments Comment[]
notifications Notification[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([role])
}
model Post {
id String @id @default(cuid())
slug String @unique
title String
body String
status String @default("DRAFT")
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Restrict)
categories CategoriesOnPosts[]
comments Comment[]
publishedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
@@index([status, publishedAt])
}
model Category {
id String @id @default(cuid())
slug String @unique
name String
posts CategoriesOnPosts[]
}
model CategoriesOnPosts {
postId String
categoryId String
assignedAt DateTime @default(now())
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
@@id([postId, categoryId])
}
model Comment {
id String @id @default(cuid())
body String
postId String
authorId String
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
author User @relation(fields: [authorId], references: [id], onDelete: Restrict)
createdAt DateTime @default(now())
@@index([postId, createdAt])
@@index([authorId])
}
model Notification {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
type String
message String
readAt DateTime?
createdAt DateTime @default(now())
@@index([userId, readAt])
}
model AuditLog {
id String @id @default(cuid())
action String
targetId String
metadata String?
createdAt DateTime @default(now())
@@index([action, createdAt])
}
Claude Code से हर index का कारण लिखवाएं। status + publishedAt public post list के लिए है, और postId + createdAt comment list के लिए। बिना real query वाला index maintenance cost है।
Migration review
npx prisma format
npx prisma migrate dev --create-only --name init_blog
# PR में prisma/migrations/*/migration.sql पढ़ें
npx prisma migrate dev
npx prisma generate
Production deploy में:
npx prisma migrate deploy
db pushprototype के लिए ठीक है, production migration workflow नहीं। Existing data परDROP, नयाNOT NULL, unique constraint और cascade change जरूर check करें।
Rollback plan PR में deploy से पहले लिखें। Migration अगर सफल हो चुकी है, तो schema वापस करके नई forward migration बनाना बेहतर है। अगर migration बीच में fail हुई है, तो Prisma के down migration workflow के अनुसार backup verify करें, migrate status चलाएं, reviewed down SQL लगाएं और उसी failed migration को migrate resolve से rolled back mark करें।
npx prisma migrate status
npx prisma migrate resolve --rolled-back "20260603090000_failed_change"
Prisma Client और transaction
// src/db.ts
import { PrismaClient } from "./generated/prisma/client";
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: ["warn", "error"],
});
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}
// src/posts.ts
import { Prisma } from "./generated/prisma/client";
import { prisma } from "./db";
export async function listPublishedPosts(params: {
page?: number;
perPage?: number;
categorySlug?: string;
search?: string;
}) {
const page = Math.max(params.page ?? 1, 1);
const perPage = Math.min(Math.max(params.perPage ?? 20, 1), 50);
const where: Prisma.PostWhereInput = {
status: "PUBLISHED",
...(params.categorySlug
? { categories: { some: { category: { slug: params.categorySlug } } } }
: {}),
...(params.search
? { OR: [{ title: { contains: params.search } }, { body: { contains: params.search } }] }
: {}),
};
const [items, total] = await prisma.$transaction([
prisma.post.findMany({
where,
skip: (page - 1) * perPage,
take: perPage,
orderBy: [{ publishedAt: "desc" }, { createdAt: "desc" }],
select: {
id: true,
slug: true,
title: true,
publishedAt: true,
author: { select: { name: true } },
_count: { select: { comments: true } },
},
}),
prisma.post.count({ where }),
]);
return { items, pagination: { page, perPage, total } };
}
export async function publishPost(postId: string) {
return prisma.$transaction(async (tx) => {
const post = await tx.post.findUnique({
where: { id: postId },
select: { id: true, title: true, status: true, authorId: true },
});
if (!post) throw new Error("Post not found");
if (post.status === "PUBLISHED") return post;
const published = await tx.post.update({
where: { id: post.id },
data: { status: "PUBLISHED", publishedAt: new Date() },
select: { id: true, title: true, status: true, publishedAt: true },
});
await tx.notification.create({
data: {
userId: post.authorId,
type: "POST_PUBLISHED",
message: `${post.title} was published`,
},
});
await tx.auditLog.create({
data: {
action: "POST_PUBLISHED",
targetId: post.id,
metadata: JSON.stringify({ title: post.title }),
},
});
return published;
});
}
List और count जैसे independent reads के लिए$transaction([])ठीक है। Publish जैसे operation में post update, notification और audit log साथ होने चाहिए, इसलिए interactive transaction उपयोगी है। Transaction के अंदर email, webhook या slow API call न रखें।
Safe SQL
पहले Prisma Client query इस्तेमाल करें। Reporting के लिए raw SQL सच में चाहिए तो pitfall string concatenation है, जिससे SQL-injection risk बनता है। Prisma की raw query docs tagged templates या Prisma.sql की सलाह देती हैं।
import { Prisma } from "./generated/prisma/client";
import { prisma } from "./db";
export async function topAuthors(limit = 10) {
return prisma.$queryRaw<
{ authorId: string; postCount: bigint }[]
>(Prisma.sql`
SELECT "authorId", COUNT(*) AS "postCount"
FROM "Post"
WHERE "status" = ${"PUBLISHED"}
GROUP BY "authorId"
ORDER BY "postCount" DESC
LIMIT ${Math.min(limit, 50)}
`);
}
Claude Code से हर $queryRawUnsafe का कारण लिखवाएं। Dynamic table या column names चाहिए हों तो human-reviewed allowlist बनाएं।
Seed, test और checklist
// prisma/seed.ts
import { PrismaClient } from "../src/generated/prisma/client";
const prisma = new PrismaClient();
async function main() {
const user = await prisma.user.upsert({
where: { email: "editor@example.com" },
update: { name: "Editor" },
create: { email: "editor@example.com", name: "Editor", role: "admin" },
});
await prisma.post.upsert({
where: { slug: "claude-code-prisma-demo" },
update: { status: "PUBLISHED", publishedAt: new Date() },
create: {
slug: "claude-code-prisma-demo",
title: "Claude Code Prisma demo",
body: "Seeded article for local verification.",
status: "PUBLISHED",
publishedAt: new Date(),
authorId: user.id,
},
});
}
main().finally(async () => prisma.$disconnect());
npm run db:migrate -- --name init_blog
npm run db:generate
npm run db:seed
npm run test
Checklist: constraints और indexes real screens से जुड़े हों, SQL destructive न हो, pagination limit हो, transaction short हो, seed दोबारा चल सके, tests rollback या duplicate execution देखें, और production मेंmigrate deployचलाया जाए।
Use cases, pitfalls और CTA
तीन practical use cases हैं: content site का article management, SaaS admin panel, और payment के बाद entitlement देना। Content site में slug और published status अहम हैं। SaaS में हर query tenant-scoped होनी चाहिए। Payment flow में idempotency, audit log और transaction boundary जरूरी हैं।
Masa ने एक छोटी blog API पर यही flow test किया। Claude Code से migration risk पहले लिखवाने पर दो issues जल्दी मिले: cascade delete ज्यादा broad था और public list जरूरत से ज्यादा author fields लौटाती थी। यही सही तरीका है: AI से speed लो, फिर type, SQL और tests से verify करो।
Self-study के लिए /products/ देखें। Team workflow, review prompts और DB migration training के लिए /training/ सही entry point है। Existing schema या AI-generated PR लेकर आने पर discussion सीधे real risks पर शुरू हो सकता है।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code first repo audit checklist: पहली edit से पहले codebase map करें
20 मिनट में scope, risk area, proof command और revenue CTA जांचने की beginner-friendly checklist.
Claude Code Harness Lite: शुरुआती बदलावों के लिए छोटा सुरक्षित ढांचा
Claude Code में पढ़ना, edit, proof, public URL और revenue CTA अलग रखने वाला शुरुआती workflow.
Claude Code Repo Map First Pass: पुराने कोडबेस को सुरक्षित तरीके से पढ़ना
Claude Code से पुराने repository को edit करने से पहले समझने का सुरक्षित तरीका: repo map, छोटी task, proof, मुफ्त PDF, Gumroad और सलाह।