এই পর্বে যা শিখবেন
- একটি নতুন project-এ বর্তমান stable Prisma বসানো, আর
latestট্যাগের ফাঁদ এড়ানো schema.prisma-র model, ঘরের ধরন আর@id,@unique,@map,@updatedAtপড়া ও লেখা- CRUD,
where,select,orderBy,take,skipআর cursor চালানো, আর প্রতিটির আসল SQL দেখা P2002আরP2025-কে API-র 409 আর 404-এ বদলানো- একটি singleton client বানিয়ে পর্ব ২৯-এর repository-তে জোড়া
১ · Prisma কী, আর আজ কোন version
পর্ব ৩৮-এ দেখেছেন, ORM মানে object আর table-এর মাঝের অনুবাদক। Prisma সেই দলের, তবে এর ধরন একটু আলাদা। এখানে class লিখে table বোঝাতে হয় না। আপনি একটি আলাদা ফাইলে দোকানের তথ্যের নকশা লেখেন, আর সেই নকশা থেকে কোড তৈরি হয়।
দর্জির মাপের খাতা ভাবুন। খাতায় একবার মাপ লেখা হলো। সেই মাপ থেকেই কাপড় কাটা হয়, আবার সেই মাপ দেখেই সহকারী জানেন কোন পকেট আছে, কোনটি নেই। schema.prisma হলো সেই খাতা। কাপড় কাটা হলো database-এর table. আর সহকারী হলো Prisma Client, যিনি খাতায় নেই এমন পকেটের কথা বললেই থামিয়ে দেন।
শুরুর আগে version দেখা দরকার, কারণ এখানে একটি আসল ফাঁদ আছে। লেখার সময়ে (নভেম্বর ২০২৬) npm-এ সত্যিকারের run:
$ npm view prisma dist-tags
{ ...
next: '8.0.0-rc.10',
prev: '7.10.1',
latest: '8.0.0-rc.29',
... } (ট্রিম করা)
$ npm view @prisma/client dist-tags
{ ... latest: '7.10.1', ... }
অর্থাৎ npm install prisma লিখলে আপনি একটি release candidate পাবেন, অথচ @prisma/client থাকবে 7-এ। দুটির version আলাদা হলে গোলমাল হবে। Prisma-র নিজের docs-ও এখন 7-এর জন্য prisma@prev লিখতে বলে। এই পর্ব তাই নির্দিষ্ট version-এ চলেছে: Prisma 7.10.1. আমাদের lab-এ CLI একবার update-এর একটি বার্তাও দেখিয়েছে, যাতে prisma@latest লেখা ছিল। সেই বার্তা না ভেবে মানবেন না।
এই পর্বের সব “সত্যিকারের run”: Node 24.15.0, Prisma 7.10.1, @prisma/adapter-pg 7.10.1, pg 8.23.1, TypeScript 5.9.3, tsx 4.23.15, Express 5.3.0, Zod 4.6.7. Database হলো আসল PostgreSQL, চলেছে npm-এর embedded-postgres 18.6.0-beta.19 প্যাকেজে। সেটি beta, তাই শুধু শেখার lab-এর জন্য। পর্ব ২৯-এর TypeScript 7.0.2 দিয়েও একই কোড type-check পাস করেছে, তবে Prisma-র docs 5.9.x-এর কথা বলে। পাতার ভেতরে Prisma চলে না, তাই নিজে চালানো যায় এমন block-গুলো “শেখার মডেল”। Version বদলাতে পারে, নিজে দেখে নিন।
এবার পুরো ছবিটি দেখুন। Prisma-র দুটি জগৎ আছে: একটি শুধু তৈরির সময়ে চলে, অন্যটি অ্যাপ চলার সময়ে।
Prisma 7-এর দুই জগৎ
Build time (CLI) and runtime (client, adapter, driver)
generate তৈরি করে কোড, db push তৈরি করে table. অ্যাপ চলার সময়ে CLI লাগে না।আগের version-গুলোতে client-এর ভেতরে Rust-এ লেখা একটি আলাদা query engine চলত। Prisma 7 (নভেম্বর ২০২৫) থেকে সেটি client-এর ডিফল্ট পথ থেকে সরে গেছে। এখন query compiler SQL বানায়, আর আসল কথা বলে সাধারণ Node driver pg. তবে “Rust-free” কথাটি client-এর জন্য। আমাদের prisma -v-এ schema engine এখনো একটি আলাদা binary হিসেবেই দেখিয়েছে, যা table বদলানোর কাজ করে।
২ · শূন্য থেকে setup
একটি খালি folder-এ শুরু করি। Prisma 7-এর দরকার Node 20.19 বা তার ওপরে, TypeScript 5.4 বা তার ওপরে, আর docs অনুযায়ী ESM project. প্রথমে package.json-এ "type": "module", তারপর install:
npm pkg set type=module
npm install -D prisma@7.10.1 typescript tsx @types/node @types/pg
npm install @prisma/client@7.10.1 @prisma/adapter-pg@7.10.1 pg dotenv
npx prisma init --datasource-provider postgresql \
--output ../generated/prisma --no-skillsসত্যিকারের run-এ init বলল (ট্রিম করা):
Initialized Prisma in your project
prisma/
schema.prisma
prisma7.config.ts
.env
.gitignore
…
Then, define your models in prisma/schema.prisma and run prisma migrate dev to apply your schema.
একটি নাম খেয়াল করুন: prisma7.config.ts. পুরোনো লেখা আর বেশিরভাগ টিউটোরিয়ালে পাবেন prisma.config.ts. Prisma 7.10.0-এর release notes অনুযায়ী নতুন নামটি এসেছে, যাতে একই project-এ Prisma 7 আর Prisma 8 পাশাপাশি থাকতে পারে, আর Prisma 7-এর CLI দিয়ে নতুন শুরু করা project এই নামই পায়। 7.10.1-এর CLI দুটো নামই খুঁজে নেয়: আগে prisma7.config.*, না পেলে পুরোনো prisma.config.*. তাই দুটি ফাইল একসাথে রাখবেন না, নইলে পুরোনোটি চুপচাপ উপেক্ষিত হবে। ভেতরে যা তৈরি হলো (মন্তব্য বাদে হুবহু):
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: process.env["DATABASE_URL"],
},
});এখানে তিনটি জিনিস আগের চেয়ে বদলেছে, আর AI প্রায়ই পুরোনোটি লেখে:
| বিষয় | Prisma 7 | পুরোনো অভ্যাস |
|---|---|---|
| Database-এর URL | config ফাইলে | schema-র url = env(...), এখন error |
.env পড়া | নিজে: import "dotenv/config" | Prisma নিজে পড়ে নিত |
| Generator | prisma-client, output বাধ্যতামূলক | prisma-client-js |
| Client-এর জায়গা | generated/prisma/client.ts | node_modules, @prisma/client থেকে import |
| সংযোগ | new PrismaClient({ adapter }) | new PrismaClient() |
প্রথম ঘরের error আমরা সত্যিই চালিয়ে দেখেছি। পুরোনো schema-য় prisma validate বলল:
error: The datasource property `url` is no longer supported in schema files.
Move connection URLs for Migrate to `prisma.config.ts` and pass either `adapter`
for a direct database connection or `accelerateUrl` for Accelerate to the
`PrismaClient` constructor.
এবার .env. এতে থাকে connection string, যা একটি secret. init-এর বানানো .gitignore এই ফাইল আর /generated/prisma, দুটোকেই বাদ দেয়। নিচের মানগুলো নকল:
DATABASE_URL="postgresql://bazar:example-secret@127.0.0.1:5432/bazarghar?schema=public"/generated/prisma Git-এ না থাকলে নতুন মেশিনে বা CI-তে client থাকে না। Install-এর পরে npx prisma generate নিজে চালাতে হবে। আর dotenv-এর লাইন ভুলে গেলে কী হয়? আমাদের run-এ DATABASE_URL ছিল undefined, pg তার ডিফল্ট ঠিকানায় গেল, আর এলো ECONNREFUSED. ভুলটি সংযোগের মনে হয়, আসল কারণ env.
৩ · schema.prisma পড়া
পর্ব ২৯ আর ৩৪-এ বাজারঘরের পণ্য ছিল হাতে লেখা SQL table-এ। এবার সেটি Prisma schema-য় লিখি। generator বলে কী তৈরি হবে, datasource বলে কোন database, আর model বলে কোন তথ্য।
generator client {
provider = "prisma-client"
output = "../generated/prisma"
}
datasource db {
provider = "postgresql"
}
enum ProductStatus {
DRAFT
ACTIVE
ARCHIVED
}
model Product {
id Int @id @default(autoincrement())
sku String @unique
name String
category String
pricePaisa Int @map("price_paisa")
stock Int @default(0)
status ProductStatus @default(ACTIVE)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("products")
}| লেখা | মানে | আসল database-এ যা হলো |
|---|---|---|
@id | Primary key | products_pkey index |
@default(autoincrement()) | নিজে থেকে বাড়া id | nextval('products_id_seq'), একটি sequence |
@unique | মান অনন্য | products_sku_key নামের unique index |
@default(0) | না দিলে এই মান | column default 0 |
@map("price_paisa") | কোডে এক নাম, column-এ আরেক | column price_paisa |
@@map("products") | Model-এর table-এর নাম | table products |
@default(now()) | তৈরির সময় | default CURRENT_TIMESTAMP |
@updatedAt | বদলের সময় | column-এ কোনো default নেই |
enum | নির্দিষ্ট মানের তালিকা | নিজস্ব type "ProductStatus" |
এক @ মানে একটি ঘরের ওপর নিয়ম, দুই @@ মানে পুরো model-এর ওপর। @map কেন? পর্ব ২৯-এর নিয়মটি মনে করুন: কোডে pricePaisa, database-এ price_paisa. @map ছাড়া column-এর নাম হতো pricePaisa, আর হাতে SQL লেখার সময় প্রতিবার quote দিতে হতো।
টেবিলের শেষের আগের সারিটি গুরুত্বপূর্ণ। @updatedAt-এর কাজ করে client, database নয়। সত্যিকারের run:
raw update changed updatedAt? false
client update changed updatedAt? true
অর্থাৎ $executeRaw বা অন্য কোনো টুল দিয়ে সারি বদলালে updatedAt পুরোনোই থাকে। Cache বা sync এর ওপর ভরসা করলে এটি মনে রাখা দরকার।
| Prisma | PostgreSQL | JavaScript-এ |
|---|---|---|
String | text | string |
Int | integer (৩২ bit) | number |
BigInt | bigint | bigint |
Float | double precision | number |
Decimal | numeric | Decimal object |
DateTime | timestamp | Date |
Boolean | boolean | boolean |
Lab-এ ঘরটি হয়েছে timestamp without time zone. পর্ব ৩৪-এর timestamptz চাইলে DateTime @db.Timestamptz লিখতে হয়। আর table বানাতে এই পর্বে চলেছে prisma db push:
$ npx prisma db push
Loaded Prisma config from prisma7.config.ts.
Prisma schema loaded from prisma\schema.prisma.
Datasource "db": PostgreSQL database "bazarghar", schema "public" at "127.0.0.1:38939"
Your database is now in sync with your Prisma schema. Done in 153ms
db push কোনো ইতিহাস রাখে না, তাই এটি শুধু নিজের মেশিনের পরীক্ষার জন্য। Production-এর পথ হলো migration, যা পর্ব ৪০-এ আসবে। Model-এর মধ্যে সম্পর্ক আসবে পর্ব ৪১-এ।
৪ · টাকা রাখবেন কীভাবে: Decimal নাকি integer paisa
চায়ের দোকানের খাতায় কেউ “সাড়ে বারো টাকা”র দশমিক নিয়ে ভাবেন না। মনে মনে হিসাব হয় পয়সায়। কম্পিউটারের জন্যও সেটিই নিরাপদ। Float-এ টাকা রাখলে floating point-এর ভুল আসে। JavaScript-এ সত্যিকারের run:
> 0.1 + 0.2
0.30000000000000004
> 19.99 * 100
1998.9999999999998
দুটি ভালো পথ আছে: Decimal, অথবা ছোট একক অর্থাৎ minor unit-এ integer. টাকার ছোট একক পয়সা, ১ টাকা = ১০০ পয়সা। তাহলে ৳১৫০.৫০ মানে 15050.
Integer paisa (Int) | Decimal | |
|---|---|---|
| JavaScript-এ | সাধারণ number, যোগ-বিয়োগ নিরাপদ | একটি object, + চলে না, .plus() লাগে |
| JSON-এ | সংখ্যা: 15050 | স্ট্রিং: "150.5" |
| ভালো কাজে | পূর্ণ পয়সার দাম, মোট, stock-এর হিসাব | হার, ভ্যাটের ভগ্নাংশ, মুদ্রা বিনিময়, গ্রামপ্রতি দর |
| ঝুঁকি | ভুল করে দশমিক ঢুকলে কেটে যেতে পারে | ভুলে Number() করলে নিরাপত্তা শেষ |
একটি Decimal(12, 2) ঘরে '150.50' রেখে সত্যিকারের run-এ পড়া হলো:
typeof row.amount → object
JSON.stringify → {"id":1,"amount":"150.5"}
row.amount.plus('0.25').toString() → 150.75
খেয়াল করুন, JSON-এ "150.5" এসেছে একটি স্ট্রিং হয়ে, শেষের শূন্যটিও নেই। Frontend-কে এটি জানতে হবে। পুরো কোর্সে বাজারঘরের দাম তাই integer paisa: pricePaisa Int. দেখানোর সময়ে ১০০ দিয়ে ভাগ, হিসাবের সময়ে সব পূর্ণসংখ্যা। Decimal ভুল নয়, শুধু আমাদের দরকারের চেয়ে ভারী।
কতটুকু পর্যন্ত ধরবে: একটি হিসাব
PostgreSQL-এর integer ৩২ bit, সর্বোচ্চ 2,147,483,647. একে পয়সা ধরলে:
একটি পণ্যের দামের জন্য ২ কোটি টাকার বেশি যথেষ্ট। কিন্তু মাসিক বিক্রির মোট এই ঘরে ধরবে, এমন নিশ্চয়তা নেই। সেখানে BigInt বিবেচনা করুন। সীমা পেরোলে আসল run-এ error এলো P2020:
Value out of range for the type: value "2147483648" is out of range for type integer
PrismaClientKnownRequestError P2020
আসল ফাঁদ: দশমিক চুপচাপ হারিয়ে গেল
TypeScript-এর চোখে Int মানে শুধু number, তাই 150.5 compile হয়ে যায়। আমরা আশা করেছিলাম Prisma থামাবে। থামায়নি। Prisma 7.10.1 আর PostgreSQL-এ সত্যিকারের run:
create({ pricePaisa: 150.4 }) → 150
create({ pricePaisa: 150.5 }) → 150
create({ pricePaisa: 151.5 }) → 151
create({ pricePaisa: 0.30000000000000004 }) → 0
কোনো error নেই, সারি তৈরি হয়ে গেল, আর দাম গোল না হয়ে কেটে গেল। এটি এই এক সেটআপের এক run-এর ফল, অন্য version-এ আচরণ আলাদা হতে পারে। শিক্ষা একই: ভরসা রাখুন প্রবেশপথে। পর্ব ২৭-এর Zod দিয়ে z.number().int() দিলে একই ভুল status 422 হয়ে ফিরল:
$ curl -X POST /products -d '{"sku":"GHI-002", … ,"pricePaisa":620.5}'
422
{"error":{"code":"VALIDATION_FAILED", … "fields":[{"path":"pricePaisa",
"message":"Invalid input: expected int, received number"}]}}
৫ · prisma generate আর type-সহ client
prisma generate schema পড়ে আপনার project-এ TypeScript ফাইল লেখে। আমাদের run:
$ npx prisma generate
✔ Generated Prisma Client (7.10.1) to .\generated\prisma in 21ms
generated/prisma/
client.ts browser.ts enums.ts models.ts commonInputTypes.ts
internal/ models/Product.ts … (ট্রিম করা; এই lab-এ মোট 162K)
এবার একটি সম্পূর্ণ query-র পথ দেখুন, খাতা থেকে শুরু করে হাতে আসা object পর্যন্ত। “পরে” চাপুন।
- Schema
model Productনকশা - Generate
generated/কোড আর type - আপনার কোড
findUniquetype যাচাই - SQL
SELECT$1 আলাদা - Object
pricePaisaদোকানের ভাষায়
model Product {
sku String @unique
name String
pricePaisa Int @map("price_paisa")
…
}
// আসল ফাইলে অনেক বেশি লাইন; ধারণাটি এমন export type ProductModel = { id: number; sku: string; name: string; pricePaisa: number; stock: number; … }
const p = await prisma.product.findUnique({
where: { sku: 'DAL-001' },
select: { id: true, name: true, pricePaisa: true },
});
SELECT "id", "name", "price_paisa" FROM "products"
WHERE ("sku" = $1 AND 1=1) LIMIT $2 OFFSET $3
params ["DAL-001","1","0"]
{ "id": 1, "name": "মসুর ডাল ১ কেজি", "pricePaisa": 15000 }
- ধাপ ১ — নকশা। Schema-য় একটি ঘরের দুটি নাম: কোডের জন্য
pricePaisa, column-এর জন্যprice_paisa. এখনো কোনো কোড নেই। - ধাপ ২ — Generate.
prisma generateschema পড়ে TypeScript ফাইল লিখল। ডানের লেখাটি ছোট করা নমুনা। মূল কথা,Intএখানেnumberহলো। - ধাপ ৩ — আপনার কোড। এই ধাপে কিছুই চলে না, compiler পড়ে।
sku-র বানান ভুল হলে, বাselect-এ অজানা ঘর দিলে, এখানেই error. - ধাপ ৪ — SQL. Client SQL বানাল, যেখানে নাম এখন
price_paisa. মান$1-এ আলাদা গেল, তাই parameterized query, SQL injection-এর পথ বন্ধ। - ধাপ ৫ — Object. সারিটি ফিরল, আর column-এর নাম আবার
pricePaisaহলো। কোডে শুধু দোকানের ভাষা, যা আপনি পর্ব ২৯-এ চেয়েছিলেন।
এর সবচেয়ে বড় উপহার হলো select অনুযায়ী ফেরত type বদলানো। নিচের block-টি generated type-এর একটি ছোট শেখার মডেল, আসল কোড নয়। চালিয়ে দেখুন, compiler কোথায় আটকায়।
type Product = {
id: number; sku: string; name: string;
pricePaisa: number; stock: number;
};
type Select = { [K in keyof Product]?: true };
type Payload<S> = { [K in keyof S & keyof Product]: Product[K] };
function findUnique<S extends Select>(args: {
where: { sku: string };
select: S;
}): Payload<S> {
const row: Product = {
id: 1, sku: args.where.sku, name: 'dal',
pricePaisa: 15000, stock: 40,
};
return row as unknown as Payload<S>;
}
const p = findUnique({
where: { sku: 'DAL-001' },
select: { name: true, pricePaisa: true },
});
console.log(p.name, p.pricePaisa);
console.log(p.stock);Compiler থামল একটি লাইনেই: prog.ts(26,15): error TS2339: Property 'stock' does not exist on type 'Payload<{ name: true; pricePaisa: true; }>'. অর্থাৎ select-এর object থেকেই ফেরত type তৈরি হয়েছে, যাতে শুধু name আর pricePaisa আছে। আগের লাইনের p.name কোনো error দেয়নি। এখানে জাদু নেই, শুধু একটি generic আর একটি mapped type. as unknown as লেখাটি এই মডেলের শর্টকাট, নিজের কাজের কোডে এটি সাবধানে ব্যবহার করবেন। ভুলটি আসল কোডে থাকলে run-এ undefined আসত, আর দাম বা stock-এর হিসাব চুপচাপ ভুল হতো। Compiler সেটি চালানোর আগেই ধরল।
আসল client-এও হুবহু একই ভাবে থামে। Prisma 7.10.1-এর generated type-এ tsc-এর সত্যিকারের run:
src/select-types.ts(8,15): error TS2339: Property 'stock' does not exist on type
'{ name: string; pricePaisa: number; }'.
src/select-types.ts(11,32): error TS2322: Type 'string' is not assignable to type 'number'.
src/select-types.ts(14,42): error TS2561: Object literal may only specify known properties,
but 'pricePoisha' does not exist in type 'ProductWhereInput'. Did you mean to write 'pricePaisa'?
তৃতীয় বার্তাটি দেখুন: বানান ভুল ধরে ঠিক নামটিও বলে দিল। কিন্তু এই পাহারা ততক্ষণ ঠিক, যতক্ষণ client নতুন। Prisma 7-এ db push আর migrate dev নিজে থেকে generate চালায় না। আমরা schema-য় weightGram যোগ করে শুধু db push চালালাম:
src/stale.ts(2,54): error TS2353: Object literal may only specify known properties,
and 'weightGram' does not exist in type 'ProductSelect<DefaultArgs>'.
Table-এ ঘর আছে, client জানে না। npx prisma generate চালাতেই error গেল। Schema বদলের পরের অভ্যাস: push, তারপর generate.
৬ · CRUD আর আসল SQL দেখা
ORM-এর সবচেয়ে বড় বিপদ হলো না দেখে বিশ্বাস করা। তাই শুরুতেই query log চালু করি। log-এ emit: 'event' দিলে প্রতিটি query একটি event হয়:
const prisma = new PrismaClient({
adapter,
log: [{ emit: 'event', level: 'query' }, 'warn'],
});
prisma.$on('query', (e) =>
console.log(`SQL> ${e.query}\nparams ${e.params} (${e.duration}ms)`));এই log-এ params-সহ আসল মান থাকে, যাতে ফোন নম্বর বা ইমেইলও আসতে পারে। Production-এ params লিখবেন না (পর্ব ৩১-এর redaction).
এবার বাজারঘরের পণ্য নিয়ে সব কাজ। প্রতিটির নিচে আসল SQL, সত্যিকারের run. পড়ার সুবিধায় বারবার আসা "public"."products". অংশ ছেঁটে দেওয়া হয়েছে, আর লম্বা তালিকায় “…” বসেছে।
const dal = await prisma.product.create({
data: { sku: 'DAL-001', name: 'মসুর ডাল ১ কেজি',
category: 'dal', pricePaisa: 15000, stock: 40 },
});INSERT INTO "products" ("sku","name","category","price_paisa","stock","status",
"created_at","updated_at")
VALUES ($1,$2,$3,$4,$5,CAST($6::text AS "ProductStatus"),$7,$8) RETURNING "id", "sku", …
params ["DAL-001","মসুর ডাল ১ কেজি","dal",15000,40,"ACTIVE","2026-11-01T17:48:27.526Z", …]
=> {"id":1,"sku":"DAL-001", … ,"pricePaisa":15000,"stock":40,"status":"ACTIVE", …}
একটি query-তেই সারি ফিরল। status আর সময় দিয়েছে client নিজে, schema-র default দেখে।
const r = await prisma.product.createMany({
data: [ /* ৬টি পণ্য, যার একটি DAL-001 ডুপ্লিকেট */ ],
skipDuplicates: true,
});INSERT INTO "products" (…) VALUES ($1,…,$8), ($9,…,$16), … ($41,…,$48) ON CONFLICT DO NOTHING
=> {"count":5}
৬টি সারি একটি মাত্র INSERT-এ গেল। ফলাফল শুধু সংখ্যা, সারি নয়। সারিসহ চাইলে createManyAndReturn আছে। skipDuplicates ডুপ্লিকেটটি চুপচাপ বাদ দিল, তাই ৬-এর জায়গায় ৫.
await prisma.product.findUnique({ where: { sku: 'NOPE' } });SELECT "id", "sku", "name", … FROM "products" WHERE ("sku" = $1 AND 1=1) LIMIT $2 OFFSET $3
params ["NOPE","1","0"]
=> null
where-এ শুধু @id বা @unique ঘর চলে। না পেলে error নয়, null. select না দিলে সব ঘর আসে।
await prisma.product.update({
where: { sku: 'TEL-001' },
data: { stock: { increment: 20 } },
});UPDATE "products" SET "stock" = ("stock" + $1), "updated_at" = $2
WHERE ("sku" = $3 AND 1=1) RETURNING "id", "sku", "stock", …
params [20,"2026-11-01T17:48:27.554Z","TEL-001"]
increment পড়ে নিয়ে যোগ করে না। হিসাবটি SQL-এর ভেতরেই হয়, তাই দুই অনুরোধ একসাথে এলেও যোগ হারায় না। updated_at client নিজে বসিয়েছে, যা অংশ ৩-এ দেখেছেন।
await prisma.product.delete({ where: { sku: 'SUG-001' } });DELETE FROM "products" WHERE ("sku" = $1 AND 1=1) RETURNING "id", "sku"
=> {"id":9,"sku":"SUG-001"}
মুছে ফেলা সারিটিই ফিরে আসে। অনেক সারির জন্য আছে updateMany আর deleteMany.
নিচে একই ধরনের query আপনি নিজে চালাতে পারেন। এটি পাতার ভেতরের SQLite, তাই সারিগুলো আসল PostgreSQL-এর lab-এর মতো সাজানো হলেও dialect একটু আলাদা। মান বদলে দেখুন।
-- findMany({ where: { category: { in: ['rice','dal'] },
-- pricePaisa: { lte: 45000 }, stock: { gt: 0 } },
-- select: {...}, orderBy: [{pricePaisa:'desc'},{id:'asc'}],
-- take: 2, skip: 1 })
SELECT id, name, price_paisa FROM products
WHERE category IN ('rice', 'dal')
AND price_paisa <= 45000 AND stock > 0
ORDER BY price_paisa DESC, id ASC
LIMIT 2 OFFSET 1;
-- update({ where: {sku}, data: { stock: { increment: 20 } } })
UPDATE products SET stock = stock + 20 WHERE sku = 'TEL-001'
RETURNING sku, stock;
-- upsert: DAL-001 already exists, so the UPDATE part runs
INSERT INTO products (sku, name, category, price_paisa, stock)
VALUES ('DAL-001', 'Masoor dal 1kg', 'dal', 15000, 5)
ON CONFLICT (sku) DO UPDATE SET stock = stock + 5
RETURNING sku, stock;
-- upsert: SUG-001 is new, so the INSERT part runs
INSERT INTO products (sku, name, category, price_paisa, stock)
VALUES ('SUG-001', 'Sugar 1kg', 'sugar', 14000, 5)
ON CONFLICT (sku) DO UPDATE SET stock = stock + 5
RETURNING sku, stock;পাতার run-এ প্রথম query দিল 5 Mung dal 1kg 19000 আর 1 Masoor dal 1kg 15000, ২টি সারি। শর্ত মেলে ৩টি পণ্যে (42000, 19000, 15000). OFFSET 1 সবচেয়ে দামিটি বাদ দিল, তাই আসল PostgreSQL-এর ফলের সাথে হুবহু মিলেছে। UPDATE দিল TEL-001 20, কারণ 0 + 20. প্রথম upsert দিল DAL-001 45, অর্থাৎ DO UPDATE চলেছে (40 + 5). দ্বিতীয়টি দিল SUG-001 5, অর্থাৎ INSERT. একই SQL, ফল নির্ভর করেছে সারিটি আগে ছিল কি না তার ওপর।
upsert: থাকলে বদলানো, না থাকলে নতুন বানানো
Upsert সবচেয়ে ভুল বোঝা কাজগুলোর একটি। সরবরাহকারীর তালিকা থেকে পণ্য আসছে: থাকলে stock বাড়াতে হবে, না থাকলে নতুন পণ্য বসাতে হবে। শর্ত মিললে Prisma কাজটি একটি মাত্র SQL-এ করে, ON CONFLICT দিয়ে। আগে DAL-001-এ stock ছিল 40, আর sequence-এর পরের id ছিল 8.
1INSERT INTO products (sku, …, stock)2VALUES ($1, …, 5)3ON CONFLICT (sku)4DO UPDATE SET stock = stock + 55RETURNING id, sku, stock;
| চাওয়া sku | |
| sequence যে id দিল | |
| DAL-001 stock | |
| SUG-001 stock |
- Client একবার ভাবল।
where-এ একটি unique ঘর, আরcreate-এ তার মান একই (DAL-001). তাই সে SQL-এ একটিSELECTনা করে সোজা ON CONFLICT পাঠাবে। - INSERT শুরু। PostgreSQL সারি বানাতে গিয়ে আগে sequence থেকে id নিল: 8. সংঘাত তখনো ধরা পড়েনি।
- সংঘাত। Unique index
products_sku_key-এDAL-001আগেই আছে। নতুন সারি বসবে না, কাজটি ON CONFLICT-এ গেল। - DO UPDATE. পুরোনো সারিতে stock = 40 + 5 = 45. কিন্তু id 8 আর ফেরত যায় না, sequence পেছনে হাঁটে না। আসল run-এর ফল ছিল
{"sku":"DAL-001","stock":45}. - দ্বিতীয় ডাক, নতুন পণ্য। এবার
SUG-001. Sequence দিল 9, কোনো সংঘাত নেই, তাইupdateঅংশটি চলেইনি। stock হলোcreate-এর 5. - ফলাফল। আসল run-এ id ছিল 9, যদিও টেবিলে পণ্য ৭টি। আগের
createMany-র বাদ পড়া ডুপ্লিকেট নিল id 7, এই upsert নিল 8. তাই id-র ফাঁক কোনো ভুল নয়, id গুনে পণ্যের সংখ্যা বলবেন না।
শর্ত না মিললে কী হয়? Docs-এর শর্ত এগুলো: create আর update-এ nested কাজ নেই, where-এ একটিই unique ঘর, আর সেই ঘরের মান where ও create-এ এক। আমরা ইচ্ছা করে create-এ ভিন্ন sku দিলাম। আসল log:
SELECT "id" FROM "products" WHERE ("sku" = $1 AND 1=1) OFFSET $2
INSERT INTO "products" (…) VALUES (…) RETURNING "id"
SELECT "id", "sku", "stock" FROM "products" WHERE "id" = $1 LIMIT $2 OFFSET $3
COMMIT
একের জায়গায় তিনটি query, আর মাঝখানে সময়ের ফাঁক। এই ফাঁকের ফল কী, একটি run-এ মাপা হলো। একই পণ্যে ১০টি অনুরোধ একসাথে পাঠানো হলো, দুই পথে:
native upsert ×10 → ok 10, stock = 10
findUnique, তারপর create ×10 → ok 1, rejected 9, codes [ 'P2002' ], stock = 1
একই কাজ, অথচ নিজে হাতে লেখা “দেখে তারপর বানাও” ১০টির ৯টিতে ব্যর্থ, আর ৯টি যোগ হারিয়ে গেছে। এই race-এর পূর্ণ আলোচনা পর্ব ৪৩-এ। এখনকার নিয়ম: unique constraint-ই আসল পাহারাদার, আগের SELECT নয়।
৭ · পড়া: where, select, orderBy, take, skip আর cursor
পর্ব ৩৪-এ filter আর pagination-এর নিয়ম শিখেছেন। এখানে দেখুন Prisma-র ভাষায় সেগুলো কী হয়। সব SQL এই lab-এর আসল log থেকে ছোট করা।
| Prisma | SQL |
|---|---|
{ category: 'dal' } | "category" = $1 |
{ category: { in: [...] } } | "category" IN ($1,$2) |
{ pricePaisa: { lte: 45000 } } | "price_paisa" <= $3 |
{ stock: { gt: 0 } } | "stock" > $4 |
{ name: { contains: 'চা পাতা' } } | "name"::text LIKE ('%' || $2 || '%') |
{ sku: { startsWith: 'tel', mode: 'insensitive' } } | "sku" ILIKE ($1 || '%') |
{ OR: [a, b] } | (a OR b) |
এক where-এর কয়েকটি শর্ত মানে AND. contains সুন্দর দেখায়, কিন্তু সামনে % থাকায় সাধারণ B-tree index কাজে আসে না, যা পর্ব ৩৪-এ দেখেছেন। বড় টেবিলে এর জন্য full-text search ভাবতে হয়।
একসাথে সব ব্যবহার করলে এমন, সত্যিকারের run:
const rows = await prisma.product.findMany({
where: {
category: { in: ['rice', 'dal'] },
pricePaisa: { lte: 45000 },
stock: { gt: 0 },
},
select: { id: true, name: true, pricePaisa: true },
orderBy: [{ pricePaisa: 'desc' }, { id: 'asc' }],
take: 2,
skip: 1,
});SELECT "id", "name", "price_paisa" FROM "products"
WHERE ("category" IN ($1,$2) AND "price_paisa" <= $3 AND "stock" > $4)
ORDER BY "price_paisa" DESC, "id" ASC LIMIT $5 OFFSET $6
params ["rice","dal",45000,0,"2","1"]
=> [{"id":5,"name":"মুগ ডাল ১ কেজি","pricePaisa":19000},
{"id":1,"name":"মসুর ডাল ১ কেজি","pricePaisa":15000}]
take হলো LIMIT, skip হলো OFFSET. select শুধু দরকারি ঘর আনে, আর ফোনের network-এ এই ছোট্ট লাইনটিই payload কমায়। উল্টো কাজের জন্য আছে omit, তবে দুটো একসাথে চলে না।
ভেতরের নিয়মগুলো চোখে দেখতে নিচে একটি ছোট নকল prisma.product. এটি শেখার মডেল, আসল Prisma নয়, তবে এর শেষ লাইনটি আসল আচরণটিই দেখায়।
const rows = [
{ id: 1, sku: 'DAL-001', category: 'dal', pricePaisa: 15000, stock: 40 },
{ id: 2, sku: 'CHL-001', category: 'rice', pricePaisa: 42000, stock: 25 },
{ id: 3, sku: 'CHL-002', category: 'rice', pricePaisa: 47500, stock: 8 },
{ id: 4, sku: 'TEL-001', category: 'oil', pricePaisa: 18500, stock: 0 },
{ id: 5, sku: 'DAL-002', category: 'dal', pricePaisa: 19000, stock: 12 },
];
const ok = (value, f) => {
if (f === undefined) return true; // undefined মানে কোনো শর্তই নেই
if (typeof f !== 'object') return value === f;
if (f.in && !f.in.includes(value)) return false;
if (f.gt !== undefined && !(value > f.gt)) return false;
return true;
};
const product = {
findMany({ where = {}, select, orderBy, take, skip = 0 }) {
let out = rows.filter((r) =>
Object.entries(where).every(([k, f]) => ok(r[k], f)));
if (orderBy) {
const [[k, dir]] = Object.entries(orderBy);
out = [...out].sort((a, b) => (dir === 'asc' ? a[k] - b[k] : b[k] - a[k]));
}
out = out.slice(skip, take === undefined ? undefined : skip + take);
if (!select) return out;
return out.map((r) => Object.fromEntries(Object.keys(select).map((k) => [k, r[k]])));
},
};
console.log(product.findMany({
where: { category: { in: ['rice', 'dal'] }, stock: { gt: 0 } },
select: { sku: true, pricePaisa: true },
orderBy: { pricePaisa: 'desc' }, take: 2, skip: 1,
}));
const sku = undefined; // ভুল করে খালি
console.log(product.findMany({ where: { sku } }).length, 'rows');প্রথম ফল [ { sku: 'CHL-001', pricePaisa: 42000 }, { sku: 'DAL-002', pricePaisa: 19000 } ]. শর্তে মিলল ৪টি সারি: 47500, 42000, 19000, 15000. skip: 1 প্রথমটি বাদ দিল, take: 2 পরের দুটি নিল, আর select শুধু দুটি ঘর রাখল। দ্বিতীয় লাইন 5 rows, যদিও আমরা একটি মাত্র sku খুঁজছিলাম। sku ছিল undefined, তাই শর্তটিই বাদ পড়ল, আর ৫টি সারিই ফিরল। এই মডেলের ক্রম আসল database-এ হয় SQL-এর ভেতরে, JavaScript-এ নয়।
undefined মানে “এই শর্ত নেই”, “মেলে না” নয়। আসল Prisma-য় সত্যিকারের run: ৭টি পণ্যের টেবিলে findMany({ where: { sku: undefined } }) দিল ৭টি সারি, আর findFirst দিল প্রথম পণ্যটি। Prisma-র docs-এর উদাহরণে deleteMany({ where: { id: undefined } }) সব সারি মুছে দেয়। ভাবুন where: { ownerId: req.user?.id }, আর login ছাড়া request এলো: সবার data বেরিয়ে যাবে। তাই নিশ্চিত হওয়া মান ছাড়া where-এ কিছু দেবেন না। Docs-এ Prisma.skip আর preview option strictUndefinedChecks-এর কথাও আছে।
Cursor: পর্ব ৩৪-এর সাথে মিলিয়ে
Prisma-য় cursor লেখা সহজ: cursor-এ শেষ সারির id, আর skip: 1 দিয়ে সেই সারিটি বাদ। না হলে সেটি আবার আসে।
const page2 = await prisma.product.findMany({
take: 2, skip: 1,
cursor: { id: 2 },
orderBy: { id: 'asc' },
select: { id: true, sku: true },
});SELECT "id", "sku" FROM "products"
WHERE "id" >= (SELECT "id" FROM "products" WHERE ("id") = ($1))
ORDER BY "id" ASC LIMIT $2 OFFSET $3
params [2,"2","1"]
=> [{"id":3,"sku":"CHL-002"},{"id":4,"sku":"TEL-001"}]
দেখুন, আসল SQL পর্ব ৩৪-এর সরল WHERE id > ? নয়। এতে একটি subquery আর OFFSET 1 আছে। ছোট অফসেট, তাই গভীরতায় খরচ বাড়ে না, তবে cursor-এ দেওয়া ঘরটি অনন্য হতে হয়। পর্ব ৩৪-এর (created_at, id) ক্রমের জন্য তাই একটি compound unique লাগবে, নয়তো নিজের where লিখতে হবে। আর hasMore এর জন্য পর্ব ৩৪-এর limit + 1 কৌশলই চলে। Client-এর পাঠানো object কখনো সরাসরি orderBy-এ দেবেন না, পর্ব ৩৪-এর allowlist এখানেও দরকার।
৮ · P2002, P2025 আর API error
পর্ব ২৮-এর error handling-এ toAppError লিখেছিলেন, যা ভেতরের ভুলকে একটি ভাষায় আনে। সেখানে কথা দেওয়া ছিল, Prisma-য় unique violation-এর code হবে P2002. Prisma-র এই ভুলগুলো আসে PrismaClientKnownRequestError হয়ে, যার code স্থির।
| Code | কখন | HTTP |
|---|---|---|
P2002 | Unique ঘরে ডুপ্লিকেট | 409 Conflict |
P2025 | update, delete, findUniqueOrThrow সারি পায়নি | 404 Not Found |
P2020 | মান ঘরের সীমার বাইরে | আগেই Zod-এ ধরুন; তবু এলে 422 |
| অচেনা | বাকি সব | 500, সাধারণ বার্তা |
এবার সবচেয়ে সূক্ষ্ম জায়গা। প্রায় সব পুরোনো টিউটোরিয়াল আর AI লেখে e.meta.target, যাতে ঘরের নাম থাকত। Docs-এর সাধারণ error পাতাতেও উদাহরণটি এখনো আছে। Driver adapter-এ আমাদের সত্যিকারের run-এ দেখা গেল অন্য আকার (trim করা):
name: PrismaClientKnownRequestError | code: P2002 | clientVersion: 7.10.1
instanceof: true
meta: {
driverAdapterError: DriverAdapterError: UniqueConstraintViolation {
cause: {
originalCode: '23505',
originalMessage: 'duplicate key value violates unique constraint "products_sku_key"',
kind: 'UniqueConstraintViolation',
constraint: { index: 'products_sku_key' },
table: 'products'
}
},
modelName: 'Product'
}
meta.target নেই। পর্ব ২৮-এ বলা SQLSTATE 23505 ভেতরে আছে, আর index-এর নামও আছে। তাই meta-র আকারের ওপর ব্যবসার নিয়ম দাঁড় করাবেন না। নিচে দুই ধরনের লেখার তুলনা। এটিও শেখার মডেল: class-টি নকল, তবে meta-র আকার ওপরের আসল run থেকে নেওয়া।
class KnownError extends Error { // নকল, আসলটি generated client-এ
constructor(code, meta) { super('prisma detail'); this.code = code; this.meta = meta; }
}
class AppError extends Error {
constructor(status, code, message) {
super(message); this.status = status; this.code = code;
}
}
function toAppError(err) {
if (err instanceof AppError) return err;
if (err instanceof KnownError) {
if (err.code === 'P2002') return new AppError(409, 'DUPLICATE_SKU', 'SKU exists');
if (err.code === 'P2025') return new AppError(404, 'PRODUCT_NOT_FOUND', 'Not found');
}
return null; // অচেনা মানে bug
}
const reply = (err) => {
const k = toAppError(err);
return { status: k?.status ?? 500, code: k?.code ?? 'INTERNAL',
message: k?.message ?? 'Something went wrong on our side' };
};
const dup = new KnownError('P2002', {
modelName: 'Product',
driverAdapterError: { cause: { originalCode: '23505',
constraint: { index: 'products_sku_key' }, table: 'products' } },
});
console.log('old habit :', dup.meta.target);
console.log('real path :', dup.meta.driverAdapterError.cause.constraint.index);
console.log(reply(dup));
console.log(reply(new KnownError('P2025', { modelName: 'Product' })));
console.log(reply(new KnownError('P2010', {})));
console.log(reply(new TypeError('secret table name in here')));old habit : undefined বলছে পুরোনো পথে কিছুই নেই, আর real path : products_sku_key বলছে তথ্যটি অন্য জায়গায়। তারপর P2002 হলো { status: 409, code: 'DUPLICATE_SKU', … }, আর P2025 হলো { status: 404, code: 'PRODUCT_NOT_FOUND', … }. শেষ দুটি লক্ষ করুন: অচেনা P2010 আর সাধারণ TypeError, দুটোই { status: 500, code: 'INTERNAL', message: 'Something went wrong on our side' }. TypeError-এর ভেতরের “secret table name” বাইরে যায়নি। আর সিদ্ধান্ত হয়েছে শুধু code দেখে, meta দেখে নয়, তাই ভেতরের আকার বদলালেও handler ভাঙে না।
একটি ডুপ্লিকেট SKU কীভাবে database থেকে উঠে 409 হয়, সেটি স্তর ধরে দেখুন।
- Request
POSTZod পাস - Repository
createINSERT - PostgreSQL
23505থামাল - Adapter
P2002অনুবাদ - toAppError
409সিদ্ধান্ত - Response
JSONনিরাপদ বার্তা
POST /products
{ "sku": "GHI-001", "name": "again",
"category": "ghee", "pricePaisa": 62000 }
create: (p) => db.product.create({ data: p, select: fields })
duplicate key value violates unique constraint
"products_sku_key"
PrismaClientKnownRequestError code: P2002 message: (ফাইলের path আর কোডের লাইনসহ লম্বা লেখা)
if (err.code === 'P2002')
return new AppError(409, 'DUPLICATE_SKU', 'এই SKU আগেই আছে', …)
409
{"error":{"code":"DUPLICATE_SKU","message":"এই SKU আগেই আছে",
"fields":[{"path":"sku","message":"এই SKU আগেই আছে"}]}}
- ধাপ ১ — Request. Zod-এর চোখে সব ঠিক:
GHI-001আকারে বৈধ। ডুপ্লিকেট কি না Zod জানে না, কারণ সে database দেখে না। - ধাপ ২ — Repository.
createচলল। এখানে আগেfindUniqueকরে দেখা হয় না, কারণ সেই যাচাই race-এ হেরে যায়। - ধাপ ৩ — PostgreSQL. Unique index সারিটি থামাল, SQLSTATE
23505. এটিই একমাত্র জায়গা, যা একসাথে আসা দুই অনুরোধেও ঠকে না। - ধাপ ৪ — Adapter আর client.
pg-এর ভুলটিP2002হলো। এরmessage-এ ফাইলের path আর কোডের লাইন থাকে, যা আমাদের run-এই দেখা গেছে। এটি কখনো বাইরে পাঠানো যাবে না। - ধাপ ৫ — toAppError. কেন্দ্রীয় handler code দেখে সিদ্ধান্ত নিল: 409. Repository বা controller-এ আলাদা
try/catchলাগল না। - ধাপ ৬ — Response. Client পেল স্থির
codeআর ভদ্র বার্তা। Log-এ রইল আসল কারণ:{"status":409,"code":"DUPLICATE_SKU","prisma":"P2002"}.
আসল Express অ্যাপে পাঁচটি request-এর সত্যিকারের run (Express 5.3.0, ট্রিম করা). id 62 এসেছে, কারণ আগের পরীক্ষাগুলো sequence খরচ করেছে:
$ POST /products {"sku":"GHI-001", …} → 201 {"id":62,"sku":"GHI-001", … }
$ POST /products {"sku":"GHI-001", …} → 409 {"error":{"code":"DUPLICATE_SKU", … }}
$ PATCH /products/GHI-001/price {"pricePaisa":64500} → 200 {"id":62, … ,"pricePaisa":64500, … }
$ PATCH /products/NOPE-999/price {"pricePaisa":100} → 404 {"error":{"code":"PRODUCT_NOT_FOUND", … }}
$ DELETE /products/NOPE-999 → 404 {"error":{"code":"PRODUCT_NOT_FOUND", … }}
Client বানাতে log-এ 'error' দিলে Prisma প্রতিটি P2002 আর P2025-ও prisma:error লাইনে ছাপে, পুরো path-সহ। অথচ এগুলো স্বাভাবিক ঘটনা, 500 নয়। আমরা তাই 'error' বাদ দিয়েছি, আর লেখার দায়িত্ব central handler-কে দিয়েছি।
৯ · একটিই client, hot reload আর repository
Client-এর ভেতরে থাকে একটি connection pool, অর্থাৎ আগে থেকে খোলা কিছু সংযোগ। তিনটি CNG-র একটি স্ট্যান্ড ভাবুন: যাত্রী অপেক্ষা করেন, গাড়ি ফিরলে পরের জন ওঠেন। Prisma 7-এ pool-টি pg-এর, তাই তার ডিফল্টই চলে: max ১০টি সংযোগ। Prisma 6-এর ডিফল্ট আলাদা ছিল, আর docs সতর্ক করে যে pg-এর সংযোগের ডিফল্ট timeout নেই।
একটি হিসাব: pool কতটা কাজ নিতে পারে
৩০টি query, প্রতিটি ১০০ ms. Pool-এ cটি সংযোগ থাকলে আদর্শ সময় হবে ⌈30 ÷ c⌉ × 100 ms:
max | হিসাব | আদর্শ | এক run-এ মাপা | সংযোগ |
|---|---|---|---|---|
| 3 | ⌈30 ÷ 3⌉ × 100 | ১,০০০ ms | ১,১৪২ ms | 3 |
| 10 | ⌈30 ÷ 10⌉ × 100 | ৩০০ ms | ৩৭৭ ms | 10 |
বাড়তি সময়টি নতুন সংযোগ খোলার আর Prisma-র নিজের খরচ, এবং এটি একটি মেশিনের এক run-এর মাপ। Pool ঠিক করতে হলে adapter-এ দিন:
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL,
max: 10,
connectionTimeoutMillis: 5000,
});Hot reload-এ সংযোগ জমে কেন
Client মানে pool, আর pool মানে খোলা সংযোগ। কিছু dev server ফাইল বদলালে পুরো process চালু রেখে module আবার চালায়, যেমন Next.js-এর dev server (পর্ব ৫১). তখন new PrismaClient() আবার চলে, কিন্তু আগের client বন্ধ হয় না। tsx watch বা node --watch সাধারণত পুরো process নতুন করে চালায়, তাই সেখানে এই সমস্যা কম। আমরা একই process-এ module বারবার import করে এই ঘটনাটি অনুকরণ করলাম, প্রতিবার ১০টি query একসাথে। সত্যিকারের run, PostgreSQL-এর max_connections 100:
সাধারণ module singleton
reload 1: clients=1 connections=10 reload 1: clients=1 connections=10
reload 2: clients=2 connections=20 reload 2: clients=1 connections=10
… …
reload 9: clients=9 connections=90 reload 9: clients=1 connections=10
reload 10: FAILED P2010 Raw query failed. reload 10: clients=1 connections=10
Code: `53300`. sorry, too many clients already …
বাঁয়ে দশম reload-এই database নতুন সংযোগ নিতে অস্বীকার করল। হিসাবটি সহজ: ৯ × ১০ = ৯০, পরীক্ষার নিজের ১টি, আর দশম client-এর ১০টির কিছু। ডানে সংখ্যাটি নড়েনি। সমাধান হলো singleton: client-কে এমন জায়গায় রাখা, যা module আবার চললেও টিকে থাকে, অর্থাৎ globalThis:
import 'dotenv/config';
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '../../generated/prisma/client.ts';
function createClient() {
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
return new PrismaClient({
adapter,
log: [{ emit: 'event', level: 'query' }, 'warn'],
});
}
type Client = ReturnType<typeof createClient>;
const globalForPrisma = globalThis as unknown as { prisma?: Client };
export const prisma = globalForPrisma.prisma ?? createClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}Adapter ছাড়া client বানাতে গেলে আসল run-এ বার্তা স্পষ্ট:
PrismaClientInitializationError: PrismaClient was instantiated without any options.
A driver adapter is required to connect to your database.
আর বন্ধ করার সময় সংযোগ ফেরত দিন। পর্ব ২৮-এর graceful shutdown-এ SIGTERM পেলে await prisma.$disconnect() ডাকুন। আমাদের run-এ সব client-এর $disconnect-এর পরে সংযোগ ছিল ০।
পর্ব ২৯-এর repository-তে জোড়া
পর্ব ২৯-এ repository-র চুক্তি ছিল interface, আর আসল কোড ছিল SQLite-এ। এবার একই ধরনের চুক্তির নিচে Prisma বসে। Service কিছুই টের পায় না।
import { Prisma, PrismaClient } from '../generated/prisma/client.ts';
import type { ProductRepository } from './product.repository.ts';
const fields = {
id: true, sku: true, name: true, category: true,
pricePaisa: true, stock: true,
} satisfies Prisma.ProductSelect;
export function prismaProductRepository(db: PrismaClient): ProductRepository {
return {
findBySku: (sku) =>
db.product.findUnique({ where: { sku }, select: fields }),
list: ({ category, limit, after }) =>
db.product.findMany({
where: { status: 'ACTIVE', category },
select: fields,
orderBy: { id: 'asc' },
take: limit,
...(after ? { cursor: { id: after }, skip: 1 } : {}),
}),
create: (p) => db.product.create({ data: p, select: fields }),
async setPrice(sku, pricePaisa) {
try {
return await db.product.update({
where: { sku }, data: { pricePaisa }, select: fields,
});
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError
&& e.code === 'P2025') return null;
throw e;
}
},
};
}চারটি সিদ্ধান্ত খেয়াল করুন। select: fields রাখায় createdAt-এর মতো ভেতরের ঘর বাইরে যায় না। db parameter-এ আসে, তাই dependency injection অটুট। P2025 এখানে null হয়েছে, কারণ পর্ব ২৯-এর চুক্তি বলে “না পেলে null”, আর 404-এর সিদ্ধান্ত repository-র নয়। আর list-এ category undefined হলে filter চলে যায়, এখানে সেটিই আমরা চাই। ঐচ্ছিক filter-এ এটি সুবিধা, বাধ্যতামূলক শর্তে এটি বিপদ।
সত্যিকারের run-এ category=dal&limit=1 দিল "hasMore":true,"next":1, আর after=1 দিয়ে পরের পাতা দিল "hasMore":false.
Prisma Studio: npx prisma studio browser-এ table দেখা আর সম্পাদনার একটি টুল খোলে। শেখার সময়ে কাজে লাগে। মনে রাখবেন, এতে হাত দিয়ে data বদলানো যায়, তাই production-এর URL দিয়ে খুলবেন না। এই পর্বের lab-এ এটি খোলা হয়নি, শুধু --help দেখা হয়েছে।
আরও গভীরে: Prisma “data mapper”-এর কাছাকাছি হলেও row-গুলো সাধারণ object, method নেই। generate আসলে compile-time metaprogramming: schema একটি ছোট ভাষা, আর generator তার compiler. Type-গুলো mapped আর conditional type-এ তৈরি, যা পর্ব ৩৭-এ দেখেছেন। তাই বড় schema-য় editor কিছুটা ধীর হতে পারে। PostgreSQL-এ sequence transaction-এর বাইরে চলে, ফলে nextval rollback হয় না। এজন্যই upsert-এর stepper-এ id নষ্ট হয়েছে।
পরীক্ষা ও ইন্টারভিউতে:
findUniqueআরfindFirst-এর পার্থক্য? প্রথমটি শুধু unique ঘরে, দ্বিতীয়টি যেকোনোwhere-এ প্রথম মিল।updateসারি না পেলে কী হয়?P2025ছোড়ে,nullদেয় না।- Prisma 7-এ
driver adapterকেন? Rust engine সরে যাওয়ায় আসল সংযোগ করে Node-এর driver, adapter তার জোড়া। - টাকা কেন
Float-এ নয়? দ্বিমিক দশমিক অনেক মান ঠিক ধরে না। Integer paisa বা Decimal নিন। schema.prismaবদলালে কী চালাবেন? Table-এর জন্য migrate বা push, client-এর জন্যprisma generate.
১০ · AI-এর যুগে Prisma
Prisma দ্রুত বদলেছে, আর AI-এর শেখা উপাদানে পুরোনো version-ই বেশি। তাই এখানে AI খুব আত্মবিশ্বাসে পুরোনো কোড লেখে। নিচের প্রতিটি আমরা 7.10.1-এ চালিয়ে বা docs-এ মিলিয়ে দেখেছি।
| AI যা লেখে | যা হয় | ঠিক পথ |
|---|---|---|
new PrismaClient() | PrismaClientInitializationError | adapter-সহ |
schema-য় url = env(...) | validate error | config ফাইলে |
prisma.$use(...) | আমাদের run-এ $use undefined, API সরানো হয়েছে | $extends |
e.meta.target | adapter-এ undefined | e.code ধরে সিদ্ধান্ত |
price Float | দশমিকের ভুল | pricePaisa Int |
আগে খুঁজে, তারপর create | ১০-এ ৯ P2002, একটি run-এ | @unique, upsert |
catch (e) { return [] } | ভুল লুকিয়ে গেল | central handler-এ পাঠান |
AI-কে কাজ দেওয়ার সময় version আর সীমা আগেই বলে দিন, যাতে সে অনুমানে পুরোনো কোড না লেখে:
আরেকটি কথা। Prisma-র CLI 6.15 থেকে AI agent চিনতে পারে, আর data মুছে দিতে পারে এমন কমান্ড সে নিজে থেকে চালায় না। আমাদের lab-এই db push --accept-data-loss থেমে গিয়ে মানুষের স্পষ্ট সম্মতি চেয়েছে। এটি ভালো নকশা, তবে একমাত্র পাহারা নয়। Production-এর URL কখনো AI-এর হাতে দেবেন না, আর সম্মতির ঘরে AI-কে নিজে লিখতে দেবেন না।
AI-এর লেখা Prisma কোড মেলানোর চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Prisma কেন নিজের Rust engine থেকে সরে এলো, আর driver adapter বাধ্যতামূলক করল? এতে কী পাওয়া গেল, কী দিতে হলো?
Prisma-র শুরুর বছরগুলোতে client-এর সাথে একটি আলাদা Rust binary চলত। JavaScript থেকে query যেত সেই binary-তে, সে SQL বানাত, database-এ যেত, আর ফল JSON হয়ে ফিরত। নকশাটির একটি বড় সুবিধা ছিল: এক engine অনেক ভাষার client-এর কাজে লাগানো যেত, আর Rust দ্রুত।
কিন্তু বাস্তবে কয়েকটি খরচ জমল। প্রথমত, প্রতিটি OS আর CPU-র জন্য আলাদা binary লাগত, যা deployment-এ “engine not found” ধরনের সমস্যা আনত। দ্বিতীয়ত, binary-র আকার serverless আর edge পরিবেশে সমস্যা করত, যেখানে bundle ছোট রাখতে হয় আর cold start গোনা হয়। তৃতীয়ত, JavaScript আর Rust-এর মাঝে প্রতিবার data পার করা বিনা খরচে হয় না। Prisma-র নিজের changelog জানায়, 7.0.0 (নভেম্বর ২০২৫) থেকে Rust-free client ডিফল্ট হয়েছে।
দাম কী? সংযোগের দায়িত্ব এখন Node-এর driver-এর। তাই pool-এর ডিফল্ট pg-এর, Prisma 6-এর নয়। Docs সতর্ক করে যে pg-এর সংযোগের ডিফল্ট timeout নেই, আর SSL সার্টিফিকেট যাচাইয়ের আচরণও বদলেছে। ফলে একটি পুরোনো অ্যাপ version বদলালে ঠিকমতো চললেও, চাপের সময়ে অন্যরকম আচরণ করতে পারে। আর আগেই দেখেছেন, e.meta-র আকারও বদলে গেছে।
এখানে একটি বড় প্রকৌশল-শিক্ষা আছে। একটি স্তর সরালে তার করা কাজগুলো উধাও হয় না, সেগুলো অন্য কারও ঘাড়ে যায়। Migration-এর কাজ এখনো একটি আলাদা schema engine-এ, তাই “Rust-free” কথাটিও পুরো CLI-র জন্য নয়।
মূল উপলব্ধি: Upgrade notes শুধু “নতুন কী” নয়, “কার দায়িত্ব কোথায় সরল” তার তালিকা। বড় version বদলে pool, timeout আর error-এর আকার নিজে মাপুন।
প্র ০২টাকার জন্য integer paisa আর Decimal-এর মধ্যে সিদ্ধান্ত কীভাবে নেবেন? একটি ছোট কাটাকাটি কি সত্যিই এত বড় ব্যাপার?
IEEE 754 মান (১৯৮৫) দশমিক সংখ্যাকে দ্বিমিকে রাখে। ১/৩ যেমন দশমিকে শেষ হয় না, 0.1 তেমনি দ্বিমিকে শেষ হয় না। তাই 0.1 + 0.2-তে ছোট ভুল আসে। একটি ভুলে ক্ষতি নেই, কিন্তু লাখ লেনদেনে জমতে থাকে।
সবচেয়ে বেশি উদ্ধৃত ঘটনাটি Vancouver Stock Exchange-এর। ১৯৮২ সালে সূচক শুরু হয়েছিল ১,০০০-এ। প্রতিটি লেনদেনের পরে সূচকের মান তিন দশমিকে গোল না করে কেটে ফেলা হতো। প্রতিবার ক্ষতি সামান্য, কিন্তু দিনে হাজার হাজার বার। প্রচলিত বিবরণ অনুযায়ী প্রায় দুই বছরে সূচক নেমে আসে ৫০০-র ঘরে, অথচ ঠিক হিসাবে তা থাকার কথা ছিল হাজারের ওপরে। সেই কাটাকাটিই আমাদের 150.5 থেকে 150 হওয়ার ঘটনার বড় রূপ।
সিদ্ধান্তের প্রশ্নটি হলো: আপনার ক্ষুদ্রতম একক কী? ISO 4217 টাকার জন্য দুই দশমিক ধরে, তাই পয়সার নিচে হিসাব না থাকলে integer সবচেয়ে সরল। যোগ-বিয়োগ নিখুঁত, JSON সংখ্যা থাকে, কোনো বাড়তি library লাগে না। কিন্তু ধরুন ৭.৫% ভ্যাট, গ্রামপ্রতি দর, বা মুদ্রা বিনিময়ের হার পয়সার ভেতরে থাকে। সেখানে Decimal, অথবা স্পষ্ট গোল করার নিয়ম লাগে। গোল করা নিজেই একটি ব্যবসার সিদ্ধান্ত: প্রতি পণ্যে, না মোটে? উত্তর একবার ঠিক করে লিখে রাখতে হয়।
আরেকটি বিষয় হলো সীমা। ৩২ bit-এ প্রায় ২ কোটি ১৪ লাখ টাকা। পণ্যের দামে যথেষ্ট, মোট বিক্রিতে নয়। JavaScript-এর number নিজে নিরাপদ পূর্ণসংখ্যা ধরে 253 পর্যন্ত, তার ওপরে BigInt.
মূল উপলব্ধি: টাকা মানে শুধু সংখ্যা নয়, একটি নিয়ম। ছোট একক, গোল করার রীতি আর সর্বোচ্চ মান, তিনটিই আগে ঠিক করুন। আর database-এর আগেই ভুল থামান।
প্র ০৩Code generation-এর ধাপটি কি দরকারি? Schema থেকে কোড বানানো, আর TypeScript-এই schema লেখা, এই দুই পথের পার্থক্য কী?
দুটি দর্শন আছে। Prisma-র পথ হলো আলাদা schema ভাষা: একটি নিজস্ব ফাইল, আর তা থেকে তৈরি কোড। Drizzle-এর মতো টুলের পথ হলো TypeScript-এই schema: table একটি সাধারণ object, আর type আসে TypeScript-এর inference থেকে। পর্ব ৩৭-এর tRPC-র সাথে তুলনা করলে বোঝা সহজ: সেখানেও চলছিল “generate করব, নাকি infer করব” এই প্রশ্ন।
Schema ভাষার সুবিধা: ফাইলটি পড়া সহজ, যাঁরা TypeScript জানেন না তাঁরাও বুঝতে পারেন, আর সব একই নিয়মে থাকে। Tool-গুলো (format, validate, migrate) এই একটি ফাইল ধরেই চলে। Generated type আগে থেকে লেখা থাকে বলে compiler-কে গভীর inference করতে হয় না।
দাম হলো একটি বাড়তি ধাপ। ভুলে গেলে client পুরোনো থাকে, যা আপনি weightGram-এর run-এ দেখেছেন। Prisma 7-এ db push আর migrate dev আর নিজে generate করে না, তাই ঝুঁকিটি স্পষ্ট হলো। Generated কোড Git-এ না থাকলে CI-তেও ধাপটি যোগ করতে হয়।
Inference-এর পথে বাড়তি ধাপ নেই, কোড বদলালেই type বদলায়। তবে table অনেক হলে compiler-এর ওপর চাপ বাড়তে পারে, আর schema-টি কিছুটা কোডের মতো পড়তে হয়। SQL-এর কাছাকাছি থাকাকে অনেকে সুবিধা মনে করেন, কেউ বোঝা মনে করেন। সমাজে এখানে একমত নেই, আর কোনোটিই সব প্রজেক্টে সেরা নয়।
আরেকটি প্রশ্ন, generated কোড Git-এ রাখবেন কি? init বাদ দেয়, কারণ এটি উৎপন্ন ফল, আর diff-এ শব্দ বাড়ায়। কেউ রাখেন যেন review-তে type-এর বদল দেখা যায়। পর্ব ৫-এর lockfile-এর সাথে মিলিয়ে ভাবুন: lockfile উৎপন্ন হলেও রাখা হয়, কারণ সে একটি সিদ্ধান্তের দলিল।
মূল উপলব্ধি: এক সত্যের জায়গা (single source of truth) ঠিক রাখাই আসল লক্ষ্য। Generate বা infer, দুটোই সেই একই কাজের দুই পথ, আর প্রতিটির খরচ ভিন্ন জায়গায় পড়ে।
প্র ০৪“আগে দেখি আছে কি না, না থাকলে বানাই” — এই সাধারণ যুক্তিটি কেন production-এ ভেঙে পড়ে? Database-এর constraint-এর ওপর এত ভরসা কেন?
মানুষের যুক্তিতে “দেখা” আর “করা” এক মুহূর্তের কাজ। সার্ভারে তা নয়। SELECT আর INSERT দুটি আলাদা query, আর মাঝে সময়ের ফাঁক আছে। পরিভাষায় একে check-then-act সমস্যা, বা TOCTOU (time of check to time of use) বলে। অপারেটিং সিস্টেমের ফাইল নিরাপত্তার আলোচনায় এই শব্দ ১৯৭০-এর দশক থেকেই পরিচিত।
আমাদের run-এই সংখ্যা দেখেছেন। ১০টি অনুরোধ একসাথে এলো, সবাই SELECT-এ দেখল সারি নেই, সবাই INSERT করতে গেল, একজন জিতল, ৯ জন P2002 পেল। ব্যবহারকারীর চোখে ৯টি ব্যর্থতা, আর stock-এর হিসাবে ৯টি ক্ষতি। Unique constraint না থাকলে অবস্থা আরও খারাপ হতো: কোনো error ছাড়াই ১০টি ডুপ্লিকেট সারি।
Application-এ নিয়ম লেখা কেন যথেষ্ট নয়? কারণ অ্যাপের একাধিক copy চলে, background job চলে, আর কেউ হাতে SQL চালান। শুধু database-ই সব লেখার একমাত্র মিলনস্থল। তাই unique, foreign key আর check constraint হলো শেষ পাহারা। পর্ব ৩৩-এর idempotency key-তেও এই একই চিন্তা ছিল।
ঠিক নকশা হলো তিন স্তর। প্রবেশপথে Zod আকার যাচাই করে, যাতে বন্ধুসুলভ বার্তা দেওয়া যায়। Database constraint সত্য রক্ষা করে। আর ভুলটি ধরে কেন্দ্রীয় handler 409-এ বদলায়। সুন্দর UX-এর জন্য আগে একটি SELECT করা যেতেই পারে, যেমন “এই নাম খালি কি?”, কিন্তু সেটি শুধু পরামর্শ, পাহারা নয়।
Native upsert এই কাজ এক statement-এ করে, তাই ১০টির ১০টিই সফল। কিন্তু সব শর্ত মিললে তবেই, নইলে Prisma আবার কয়েকটি query-তে ভাঙে, যা log-এ দেখেছেন। অনেক সারি আর হিসাব জড়ালে লাগে transaction, যা পর্ব ৪৩-এর বিষয়।
মূল উপলব্ধি: যে নিয়ম কখনো ভাঙা চলবে না, সেটি থাকবে database-এ। কোডের যাচাই ভদ্রতা, constraint হলো আইন।
প্র ০৫ORM-এর বানানো SQL কতটা বিশ্বাস করবেন? কখন client ছেড়ে নিজে SQL লিখবেন, আর তখন injection থেকে কীভাবে বাঁচবেন?
Query log খুললে Prisma-র SQL একটু অদ্ভুত লাগে। WHERE ("sku" = $1 AND 1=1), LIMIT $2 OFFSET $3, আর count-এর জন্য একটি subquery. Cursor-এ WHERE "id" >= (SELECT ...). এগুলো মানুষের হাতে লেখা SQL নয়, একটি সাধারণ যন্ত্রের ফল, যাকে সব ধরনের query সামলাতে হয়। বেশিরভাগ ক্ষেত্রে PostgreSQL-এর planner এগুলো সহজ করে নেয়, তাই অদ্ভুত দেখানো মানেই ধীর নয়। সিদ্ধান্ত হয় মেপে, পর্ব ৩৪-এর EXPLAIN দিয়ে, চেহারা দেখে নয়।
২০০৬ সালে Ted Neward ORM-কে “computer science-এর Vietnam” বলে একটি বিখ্যাত প্রবন্ধ লেখেন, আর Joel Spolsky-র “leaky abstraction” কথাটিও (২০০২) এখানে খাটে। দুটো কথার সারমর্ম একই: সহজ কাজ সহজ হয়, কিন্তু কঠিন কাজে নিচের স্তরটি জানতেই হয়। তাই SQL জানা ORM-এর বিকল্প নয়, পূর্বশর্ত।
কখন নিজে লিখবেন? জটিল রিপোর্ট, window function, ডেটাবেস-নির্দিষ্ট সুবিধা, বা মাপে যে query ধীর। তখন Prisma-র $queryRaw আছে। এটি একটি tagged template, তাই ভেতরের ${sku} নিজে থেকেই parameter হয়। আমাদের run-এর log-এ গেল WHERE sku = $1, আর মান আলাদা। বিপদ হলো $queryRawUnsafe বা নিজে স্ট্রিং জোড়া দেওয়া, যেখানে SQL injection ফিরে আসে। একটি ছোট ফাঁদ: সেখানে column-এর নাম parameter হতে পারে না, যা পর্ব ৩৪-এর ORDER BY সমস্যাই।
এক run-এ দেখা গেছে, pg_sleep-এর মতো void ফেরত দেওয়া কলাম $queryRaw পড়তে পারেনি, ::text cast লেগেছে। অর্থাৎ raw পথে type-এর দায়িত্বও আপনার।
মূল উপলব্ধি: ORM একটি সুবিধা, চুক্তি নয়। ৯০% কাজ client-এ রাখুন, log দেখুন, মাপুন। আর যেখানে SQL-ই সহজ, সেখানে parameter-সহ নিজে লিখুন।
অনুশীলন
হিসাব, সমালোচনা আর ফল অনুমান। শেষেরটি নিজের মেশিনে চালাতে হবে।
-
হিসাব: (ক) ৳৮৯৯.৯৯ দামের একটি পণ্যের
pricePaisaকত? ২৪০০০টি বিক্রি হলে মোট পয়সা কত, আর সেটি কিInt-এ ধরবে? (খ) Pool-এ ৫টি সংযোগ, ৪০টি query, প্রতিটি ৫০ ms. আদর্শ সময় কত? (গ) PostgreSQL-এরmax_connections১০০, আর ৪টি অ্যাপ server-এর প্রতিটিতে pool ৩০. সবাই পুরো ব্যবহার করলে কী হবে?(ক) ৮৯৯.৯৯ × ১০০ =
89999পয়সা। মোট 89,999 × 24,000 =2,159,976,000পয়সা, অর্থাৎ ৳২,১৫,৯৯,৭৬০। এটিInt-এর সীমা 2,147,483,647 ছাড়িয়ে গেছে। তাই মোটের জন্যBigIntলাগবে। JavaScript-এরnumber-এ অবশ্য এটি নিরাপদ।(খ) ⌈40 ÷ 5⌉ × 50 = 8 × 50 = ৪০০ ms. বাস্তবে আরও কিছু বেশি, কারণ সংযোগ খোলার খরচ আছে।
(গ) ৪ × ৩০ = ১২০ সংযোগ, সীমা ১০০. ২০টি সংযোগ
53300 too many clientsপাবে, আর admin-এর জন্য সংরক্ষিত সংযোগ বাদ দিলে আরও আগেই। সমাধান: প্রতি server-এ pool কমানো (যেমন ২০), অথবা PgBouncer-এর মতো একটি connection pooler বসানো। -
নকশা সমালোচনা: AI একটি কোড দিল। কমপক্ষে ছয়টি সমস্যা ধরুন।
// schema: model Product { id Int @id @default(autoincrement()) // sku String price Float stock Int } import { PrismaClient } from '@prisma/client'; app.post('/products', async (req, res) => { const prisma = new PrismaClient(); const found = await prisma.product.findFirst({ where: { sku: req.body.sku } }); if (found) return res.status(400).send('exists'); try { res.json(await prisma.product.create({ data: req.body })); } catch (e) { res.status(500).json({ error: e.message }); } });sku-তে@uniqueনেই: race-এ ডুপ্লিকেট চুপচাপ ঢুকবে।price Float: দশমিকের ভুল।pricePaisa Intদরকার।- পুরোনো import আর adapter নেই: Prisma 7-এ চলবেই না।
- প্রতি request-এ নতুন client: প্রতিবার নতুন pool, সংযোগ জমবে, বন্ধও হয় না।
findFirst, তারপরcreate: race. ডুপ্লিকেটের status-ও 400 নয়, 409.data: req.body: যাচাই নেই, mass assignment-এর ঝুঁকি।req.body.skuundefinedহলেfindFirstযেকোনো একটি সারি পাবে, আর ভুলভাবে “exists” বলবে।e.messageবাইরে: এতে ফাইলের path আর কোডের লাইন থাকে। সব ভুলের জন্য 500-ও ভুল।
-
ফল অনুমান: একটি খালি
productsটেবিল, id-র sequence ১ থেকে।skuunique. নিচের কাজগুলো পরপর হলে শেষে সারিগুলো আরr-এর মান কী?await prisma.product.create({ data: { sku: 'A', stock: 1, /*…*/ } }); const r = await prisma.product.createMany({ data: [{ sku: 'A', stock: 9 /*…*/ }, { sku: 'B', stock: 2 /*…*/ }], skipDuplicates: true, }); await prisma.product.upsert({ where: { sku: 'B' }, create: { sku: 'B', stock: 100 /*…*/ }, update: { stock: { increment: 3 } }, }); await prisma.product.upsert({ where: { sku: 'C' }, create: { sku: 'C', stock: 7 /*…*/ }, update: { stock: { increment: 3 } }, });r={ count: 1 }, কারণAবাদ পড়ল।- id 1:
A, stock 1.createManyএকে ছোঁয়নি, তাই 9 বসেনি। - id 3:
B, stock 2 + 3 = 5.create-এর 100 চলেনি। - id 5:
C, stock 7.
id-র হিসাব:
Aপেল 1.createMany-তে বাদ পড়াAখেল 2,Bপেল 3. প্রথম upsert-এর সংঘাত খেল 4 (আসল run-এ ঠিক এভাবেই 8 নষ্ট হয়েছে).Cপেল 5. একই ধরনের ক্রম lab-এ চলেছে, তবে id-র সংখ্যা sequence-এর আচরণ, এর ওপর নির্ভর করে কোনো যুক্তি লিখবেন না। - id 1:
-
কমান্ড: নিজের মেশিনে একটি PostgreSQL-এ
Categorymodel যোগ করুন, যাতেslugunique আরnameথাকে।db pushচালান, তারপরgenerateনা করেprisma.categoryব্যবহার করুন। কী দেখলেন? তারপর কোন কমান্ড সমাধান করবে, আর query log-এfindUnique-এর SQL কেমন হবে?Generate-এর আগে
tscবলবেprisma.categoryনেই (Property 'category' does not existধরনের বার্তা). কারণ table আছে, কিন্তু client পুরোনো। সমাধানnpx prisma generate. তারপরfindUnique({ where: { slug } })-এর SQL হবেSELECT … FROM "Category" WHERE ("slug" = $1 AND 1=1) LIMIT $2 OFFSET $3ধরনের (নমুনা).@@mapনা দিলে table-এর নাম হবে"Category", বড় হাতের C-সহ, যা হাতে SQL লেখার সময় quote চায়। তাই@@map("categories")দেওয়া ভালো।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি নতুন project-এ প্রথম query পর্যন্ত ধাপগুলো সাজান (▲▼ চেপে সরান)
prisma,@prisma/client, adapter আরpginstallprisma initদিয়ে schema আর config ফাইল তৈরি.env-এDATABASE_URLআর schema-য় model লেখা- Table বানাতে
db pushবা migrate চালানো prisma generateদিয়ে client তৈরি- Adapter-সহ
PrismaClientবানিয়ে query চালানো
Prisma 7-এ schema-য় একটি ঘর যোগ করে prisma db push চালানো হলো। কোডে নতুন ঘরটি ব্যবহার করতে TypeScript error দিচ্ছে। সবচেয়ে সম্ভাব্য কারণ কী?
- Database-এ ঘরটি তৈরি হয়নি
prisma generateচালানো হয়নি, client পুরোনো- Node-এর version পুরোনো
- Driver adapter দেওয়া হয়নি
৳১,২৫০.৭৫ দামের একটি পণ্য pricePaisa Int ঘরে রাখতে হবে। কোন মানটি ঠিক?
1250.7512501250751250750
Prisma-র update এমন একটি sku-তে চলল, যা টেবিলে নেই। কী হবে?
P2025error ছোড়া হবেnullফেরত আসবে- নতুন সারি তৈরি হবে
P2002error ছোড়া হবে
৭টি সারির টেবিলে const id = undefined; রেখে prisma.product.findMany({ where: { sku: id } }) চলল। কী ফেরত আসার কথা?
- খালি array
- একটি validation error
- শুধু যেসব সারিতে
skuখালি - ৭টি সারিই
Node অ্যাপে API-র জন্য একটি পণ্য তৈরির সময়ে ডুপ্লিকেট SKU আটকানোর সবচেয়ে নির্ভরযোগ্য উপায় কোনটি?
- Create-এর আগে
findUniqueকরে দেখা @uniqueরাখা, আরP2002-কে 409-এ বদলানো- Zod schema-য়
skuবাধ্যতামূলক করা - Frontend-এ বোতাম বন্ধ করে দেওয়া
Next.js-এর dev server-এ কাজ করতে করতে কিছুক্ষণ পরে too many clients already আসছে। Prisma Client একটি module-এ new PrismaClient(...) দিয়ে বানানো। প্রথমে কী দেখবেন?
max_connectionsবাড়ানো- Query-গুলো ছোট করা
- Client-কে
globalThis-এ singleton করা - Prisma Studio দিয়ে table দেখা
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Version আগে:
npm view prisma dist-tagsদেখুন, আরprismaও@prisma/clientএকই stable version-এ রাখুন। - Prisma 7:
prisma-clientgenerator, নিজের folder-এ client, driver adapter, আর URL config ফাইলে। - Schema বদলালে table আর client, দুটোই আলাদা করে মেলাতে হয়।
generateভুলবেন না। - টাকা
Intpaisa-তে, আর প্রবেশপথে Zod-এর.int(). Query log দেখে আসল SQL মেলান। P2002→ 409,P2025→ 404,undefinedমানে শর্ত নেই, আর একটিই singleton client.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪০ · Migration আর seed — schema বদলের নিরাপদ পথপরবর্তী পর্বSchema বদলাবেই। চলমান অ্যাপের ডেটা না হারিয়ে কীভাবে বদলাবেন, তা এই পর্বে।
- ফুল-স্ট্যাক ওয়েব ফ্রেমওয়ার্ক · কোর্স হোম৯৩টি পর্বপুরো কোর্সের রোডম্যাপ। পরের পর্ব ৪০: Migration আর seed.
- পর্ব ২৯ · Backend-এর গঠনআগের পর্বRepository-র চুক্তি আর dependency injection.
- পর্ব ৩৪ · Pagination, filter আর sortআগের পর্বOffset, cursor আর allowlist.
- DBMS & SQL · ORM আর migrationগভীরে যানORM-এর তত্ত্ব আর SQL-এর দিক।
- Cybersecurity · Injection প্রতিরোধপাশের ট্র্যাকRaw SQL লিখলে parameter কেন দরকার।