PostgreSQL के साथ बूंदा बांदी ORM: संपूर्ण गाइड
ड्रिज़ल ओआरएम टाइपस्क्रिप्ट डेटाबेस पारिस्थितिकी तंत्र में एक अद्वितीय स्थान रखता है: यह ओआरएम है जो वास्तव में भेस में सिर्फ एक क्वेरी बिल्डर है, स्कीमा-ए-कोड के साथ जो एसक्यूएल उत्पन्न करता है जिसे आप पढ़ सकते हैं, और एक माइग्रेशन सिस्टम जो आपके डेटाबेस को सत्य के स्रोत की तरह मानता है। प्रिज्मा और टाइपओआरएम के वर्षों के बाद, ड्रिज़ल को कच्चे एसक्यूएल के घर आने जैसा महसूस होता है - लेकिन पूर्ण टाइपस्क्रिप्ट अनुमान और शून्य रनटाइम ओवरहेड के साथ।
यह मार्गदर्शिका प्रारंभिक सेटअप से लेकर उत्पादन पैटर्न तक, 65+ ड्रिज़ल स्कीमा फ़ाइलों वाले कोडबेस से ड्राइंग, मल्टी-टेनेंट क्वेरीज़ और एक आलसी प्रॉक्सी कनेक्शन पैटर्न को कवर करती है जो नेक्स्ट.जेएस और नेस्टजेएस दोनों संदर्भों में विश्वसनीय रूप से काम करता है।
मुख्य बातें
- डीबी कनेक्शन के लिए आलसी प्रॉक्सी का उपयोग करें - मॉड्यूल लोड समय पर कभी भी उत्सुक कनेक्शन न बनाएं
- सिस्टम इंस्टॉलेशन के साथ टकराव से बचने के लिए स्थानीय पोस्टग्रेएसक्यूएल को गैर-डिफ़ॉल्ट पोर्ट (5433) पर चलना चाहिए
- बहु-किरायेदारी के लिए सभी तालिकाओं को
organizationIdकी आवश्यकता होती है - स्कीमा स्तर पर लागू करें- ड्रिज़ल एनम तुलनाओं के लिए टाइप संकुचन के लिए स्पष्ट टाइपस्क्रिप्ट कास्टिंग की आवश्यकता होती है
- कभी भी
sql.raw()का उपयोग न करें - हमेशा पैरामीटरयुक्त मानों के साथsqlटेम्पलेट शाब्दिक का उपयोग करें- संबंध विदेशी कुंजियों से अलग हैं - पूर्ण प्रकार की सुरक्षा के लिए दोनों को परिभाषित करें
- लेन-देन बहु-चरणीय संचालन को लपेटता है;
txऑब्जेक्ट को सेवाओं तक पास करें- स्कीमा इंडेक्स (
schema/index.ts) को काम पर माइग्रेशन के लिए सभी तालिकाओं को निर्यात करना होगा
स्थापना और सेटअप
ड्रिज़ल को दो पैकेजों की आवश्यकता होती है: कोर ORM और डेटाबेस ड्राइवर।
pnpm add drizzle-orm pg
pnpm add -D drizzle-kit @types/pg
माइग्रेशन और स्टूडियो सहित पूर्ण स्टैक के लिए:
pnpm add drizzle-orm postgres
pnpm add -D drizzle-kit
postgres पैकेज (pg से भिन्न) आधुनिक टाइपस्क्रिप्ट परियोजनाओं में ड्रिज़ल के लिए अनुशंसित ड्राइवर है - यह कनेक्शन पूलिंग, तैयार स्टेटमेंट का समर्थन करता है, और इसमें बेहतर टाइपस्क्रिप्ट प्रकार हैं।
आलसी प्रॉक्सी पैटर्न
नेक्स्ट.जेएस या नेस्टजेएस मोनोरेपो में ड्रिज़ल के साथ सबसे महत्वपूर्ण वास्तुशिल्प निर्णय कभी भी उत्सुक डेटाबेस कनेक्शन नहीं बनाना है। मॉड्यूल लोड समय पर उत्सुक कनेक्शन निम्न समस्याओं का कारण बनते हैं:
- Next.js: मॉड्यूल निर्माण प्रक्रिया के दौरान आयात किए जाते हैं, जहां कोई DB उपलब्ध नहीं है
- NestJS: पैकेज आयात पर्यावरण चर लोड होने से पहले होता है
- सर्वर रहित: ओवरहेड कनेक्शन के कारण कोल्ड स्टार्ट में देरी होती है
आलसी प्रॉक्सी पैटर्न इन सभी को हल करता है:
// packages/db/src/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
let _db: ReturnType<typeof drizzle> | null = null;
function getDb() {
if (!_db) {
const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
throw new Error('DATABASE_URL environment variable is not set');
}
const client = postgres(connectionString, {
max: 10, // connection pool size
idle_timeout: 20,
connect_timeout: 10,
});
_db = drizzle(client, { schema });
}
return _db;
}
// Export a Proxy that initializes the connection on first use
export const db = new Proxy({} as ReturnType<typeof drizzle>, {
get(_, prop) {
return getDb()[prop as keyof ReturnType<typeof drizzle>];
},
});
export * from './schema';
इस पैटर्न का मतलब है कि @ecosire/db आयात करने से डेटाबेस कनेक्शन कभी नहीं खुलता है। कनेक्शन तभी बनता है जब पहली क्वेरी चलती है।
स्कीमा डिज़ाइन
ड्रिज़ल स्कीमा सादे टाइपस्क्रिप्ट फ़ाइलें हैं जो तालिका परिभाषाओं को निर्यात करती हैं। यहां अनुशासन संबंधित तालिकाओं को एक ही फ़ाइल में रखना और एक केंद्रीय सूचकांक के माध्यम से सब कुछ निर्यात करना है।
// packages/db/src/schema/contacts.ts
import {
pgTable,
uuid,
text,
varchar,
timestamp,
pgEnum,
boolean,
index,
} from 'drizzle-orm/pg-core';
export const contactTypeEnum = pgEnum('contact_type', ['individual', 'company', 'partner']);
export const contacts = pgTable(
'contacts',
{
id: uuid('id').defaultRandom().primaryKey(),
organizationId: uuid('organization_id').notNull(), // Multi-tenancy
name: varchar('name', { length: 255 }).notNull(),
email: varchar('email', { length: 255 }),
phone: varchar('phone', { length: 50 }),
type: contactTypeEnum('type').default('individual').notNull(),
isActive: boolean('is_active').default(true).notNull(),
notes: text('notes'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
},
(table) => ({
organizationIdx: index('contacts_organization_idx').on(table.organizationId),
emailIdx: index('contacts_email_idx').on(table.email),
})
);
export type Contact = typeof contacts.$inferSelect;
export type NewContact = typeof contacts.$inferInsert;
$inferSelect और $inferInsert प्रकार आपको पूर्ण टाइपस्क्रिप्ट अनुमान देते हैं - ड्रिज़ल को पता है कि आपके स्कीमा बाधाओं के आधार पर सम्मिलित समय पर कौन से फ़ील्ड आवश्यक बनाम वैकल्पिक हैं।
// packages/db/src/schema/index.ts
export * from './contacts';
export * from './orders';
export * from './licenses';
export * from './products';
// ... all tables
माइग्रेशन जनरेशन के दौरान उन्हें लेने के लिए इंडेक्स फ़ाइल को drizzle-kit के लिए प्रत्येक तालिका को निर्यात करना होगा।
रिश्ते
ड्रिज्ज़ल विदेशी कुंजी बाधाओं (पोस्टग्रेएसक्यूएल द्वारा लागू) को संबंध परिभाषाओं से अलग करता है (ड्रिज़ल के रिलेशनल क्वेरी एपीआई द्वारा प्रयुक्त)। आपको दोनों की आवश्यकता है:
// packages/db/src/schema/relations.ts
import { relations } from 'drizzle-orm';
import { contacts } from './contacts';
import { orders } from './orders';
import { orderItems } from './order-items';
import { products } from './products';
export const contactsRelations = relations(contacts, ({ many }) => ({
orders: many(orders),
}));
export const ordersRelations = relations(orders, ({ one, many }) => ({
contact: one(contacts, {
fields: [orders.contactId],
references: [contacts.id],
}),
items: many(orderItems),
}));
export const orderItemsRelations = relations(orderItems, ({ one }) => ({
order: one(orders, {
fields: [orderItems.orderId],
references: [orders.id],
}),
product: one(products, {
fields: [orderItems.productId],
references: [products.id],
}),
}));
परिभाषित संबंधों के साथ, आप ड्रिज़ल की रिलेशनल क्वेरी एपीआई का उपयोग कर सकते हैं:
// Fetch order with contact and items in one query
const order = await db.query.orders.findFirst({
where: eq(orders.id, orderId),
with: {
contact: true,
items: {
with: {
product: true,
},
},
},
});
ड्रिज़ल एकल JOIN क्वेरी उत्पन्न करता है - कोई N+1 समस्या नहीं।
टाइप-सुरक्षित प्रश्न
ड्रिज़ल की क्वेरी एपीआई पूरी तरह से सुरक्षित है। आपके द्वारा शामिल किए गए कॉलम के आधार पर चयन प्रकार संकीर्ण होता है:
import { db } from '@ecosire/db';
import { contacts } from '@ecosire/db/schema';
import { eq, and, like, sql, count } from 'drizzle-orm';
// Full select — returns Contact[]
const allContacts = await db
.select()
.from(contacts)
.where(eq(contacts.organizationId, orgId))
.limit(50);
// Partial select — returns only specified columns
const contactNames = await db
.select({
id: contacts.id,
name: contacts.name,
email: contacts.email,
})
.from(contacts)
.where(
and(
eq(contacts.organizationId, orgId),
eq(contacts.isActive, true)
)
);
// Count query
const [{ total }] = await db
.select({ total: count() })
.from(contacts)
.where(eq(contacts.organizationId, orgId));
// Search with ILIKE (case-insensitive)
const searchResults = await db
.select()
.from(contacts)
.where(
and(
eq(contacts.organizationId, orgId),
like(contacts.name, `%${searchTerm}%`)
)
)
.limit(20);
डायनामिक व्हेयर क्लॉज़ (यूआई से फ़िल्टर) के लिए, सशर्त रूप से स्थितियाँ बनाएँ:
import { SQL } from 'drizzle-orm';
async function searchContacts(orgId: string, filters: {
search?: string;
type?: 'individual' | 'company';
isActive?: boolean;
}) {
const conditions: SQL[] = [eq(contacts.organizationId, orgId)];
if (filters.search) {
conditions.push(like(contacts.name, `%${filters.search}%`));
}
if (filters.type) {
// Enum comparisons need explicit casting
conditions.push(eq(contacts.type, filters.type as 'individual' | 'company' | 'partner'));
}
if (filters.isActive !== undefined) {
conditions.push(eq(contacts.isActive, filters.isActive));
}
return db
.select()
.from(contacts)
.where(and(...conditions))
.limit(100);
}
एनम मानों पर स्पष्ट कास्ट पर ध्यान दें: filters.type as 'individual' | 'company' | 'partner'। ड्रिज़ल का एनम प्रकार कास्ट के बिना संकीर्ण स्ट्रिंग उपप्रकारों को स्वचालित रूप से स्वीकार नहीं करता है।
सम्मिलित करें, अद्यतन करें और हटाएँ
import { db } from '@ecosire/db';
import { contacts, NewContact } from '@ecosire/db/schema';
import { eq, and } from 'drizzle-orm';
// Insert with RETURNING
async function createContact(data: NewContact) {
const [created] = await db
.insert(contacts)
.values(data)
.returning();
return created; // Fully typed as Contact
}
// Upsert (insert or update on conflict)
async function upsertContact(data: NewContact) {
const [result] = await db
.insert(contacts)
.values(data)
.onConflictDoUpdate({
target: [contacts.organizationId, contacts.email],
set: {
name: data.name,
updatedAt: new Date(),
},
})
.returning();
return result;
}
// Update with RETURNING
async function updateContact(
id: string,
orgId: string,
updates: Partial<NewContact>
) {
const [updated] = await db
.update(contacts)
.set({ ...updates, updatedAt: new Date() })
.where(
and(
eq(contacts.id, id),
eq(contacts.organizationId, orgId) // Always filter by org
)
)
.returning();
return updated;
}
// Soft delete (recommended over hard delete)
async function deleteContact(id: string, orgId: string) {
return db
.update(contacts)
.set({ isActive: false, updatedAt: new Date() })
.where(
and(
eq(contacts.id, id),
eq(contacts.organizationId, orgId)
)
);
}
INSERT और UPDATE स्टेटमेंट पर हमेशा .returning() का उपयोग करें - यह बनाए गए/अपडेट किए गए रिकॉर्ड को प्राप्त करने के लिए दूसरी SELECT राउंड-ट्रिप से बचता है।
लेन-देन
बहु-चरणीय संचालन जिन्हें एक साथ सफल या विफल होना चाहिए, उन्हें लेनदेन की आवश्यकता होती है:
async function createOrderWithItems(
orderData: NewOrder,
items: NewOrderItem[]
) {
return db.transaction(async (tx) => {
// Create the order
const [order] = await tx
.insert(orders)
.values(orderData)
.returning();
// Create order items
const createdItems = await tx
.insert(orderItems)
.values(items.map((item) => ({ ...item, orderId: order.id })))
.returning();
// Update inventory (must succeed or entire transaction rolls back)
for (const item of items) {
await tx
.update(products)
.set({
stock: sql`${products.stock} - ${item.quantity}`,
})
.where(eq(products.id, item.productId));
}
return { order, items: createdItems };
});
}
लेनदेन संदर्भ को सेवा विधियों में पास करते समय:
// Service accepts optional transaction for composability
async function activateLicense(
licenseId: string,
tx?: typeof db | Parameters<Parameters<typeof db.transaction>[0]>[0]
) {
const queryRunner = tx || db;
return queryRunner
.update(licenses)
.set({ status: 'active', activatedAt: new Date() })
.where(eq(licenses.id, licenseId))
.returning();
}
बूंदाबांदी-किट के साथ प्रवास
drizzle-kit CLI स्कीमा परिवर्तनों से माइग्रेशन उत्पन्न करता है:
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/schema/index.ts',
out: './src/migrations',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,
});
कार्यप्रवाह:
# Generate migration SQL from schema changes
npx drizzle-kit generate
# Apply migrations to database
npx drizzle-kit migrate
# Push schema directly (dev only — skips migration files)
npx drizzle-kit push
# Open Drizzle Studio (visual database browser)
npx drizzle-kit studio
उत्पादन के लिए, हमेशा migrate (push नहीं) का उपयोग करें। push कमांड स्थानीय विकास पुनरावृत्ति के लिए है - यह माइग्रेशन फ़ाइलें नहीं बनाता है, इसलिए परिवर्तनों को ट्रैक नहीं किया जाता है।
पेजिनेशन पैटर्न
कुशल पृष्ठांकन के लिए डेटा क्वेरी और गिनती क्वेरी दोनों की आवश्यकता होती है:
interface PaginationParams {
page: number;
pageSize: number;
}
async function getContactsPaginated(orgId: string, { page, pageSize }: PaginationParams) {
const offset = (page - 1) * pageSize;
const [data, [{ total }]] = await Promise.all([
db
.select()
.from(contacts)
.where(eq(contacts.organizationId, orgId))
.limit(pageSize)
.offset(offset)
.orderBy(contacts.createdAt),
db
.select({ total: count() })
.from(contacts)
.where(eq(contacts.organizationId, orgId)),
]);
return {
data,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize),
},
};
}
दोनों प्रश्नों को Promise.all के समानांतर चलाने से अनुक्रमिक निष्पादन की तुलना में प्रतिक्रिया समय आधा हो जाता है।
सामान्य नुकसान और समाधान
नुकसान 1: गतिशील प्रश्नों के लिए sql.raw() का उपयोग करना
// Dangerous — SQL injection vulnerability
const results = await db.execute(
sql.raw(`SELECT * FROM contacts WHERE name = '${userInput}'`)
);
// Safe — parameterized
const results = await db.execute(
sql`SELECT * FROM contacts WHERE name = ${userInput}`
);
नुकसान 2: अक्सर पूछे जाने वाले कॉलमों पर गुम अनुक्रमणिका
जिन कॉलमों को आप फ़िल्टर करते हैं उनमें हमेशा इंडेक्स जोड़ें। सबसे आम छूटे हुए सूचकांक:
(table) => ({
organizationIdx: index('contacts_org_idx').on(table.organizationId),
emailIdx: index('contacts_email_idx').on(table.email),
compositeIdx: index('contacts_org_active_idx').on(
table.organizationId,
table.isActive
),
})
नुकसान 3: मैन्युअल संबंध लोडिंग से एन+1 प्रश्न
// N+1 problem — one query per order
const orders = await db.select().from(ordersTable);
for (const order of orders) {
order.contact = await db
.select()
.from(contacts)
.where(eq(contacts.id, order.contactId));
}
// Solution — use Drizzle's relational query API
const orders = await db.query.orders.findMany({
with: { contact: true },
});
नुकसान 4: schema/index.ts से नई तालिकाएँ निर्यात करना भूल जाना
यदि आप एक नई स्कीमा फ़ाइल बनाते हैं लेकिन schema/index.ts से पुनः निर्यात करना भूल जाते हैं, तो drizzle-kit नई तालिका नहीं देख पाएगा और माइग्रेशन उत्पन्न नहीं होगा।
अक्सर पूछे जाने वाले प्रश्न
टाइपस्क्रिप्ट परियोजनाओं के लिए ड्रिज़ल की तुलना प्रिज्मा से कैसे की जाती है?
टाइपस्क्रिप्ट प्रकारों के साथ ड्रिज़ल कच्चे एसक्यूएल के करीब है - आप क्वेरी लिखते हैं जो 1: 1 से एसक्यूएल तक मैप करते हैं। कस्टम क्वेरी भाषा और एक अलग स्कीमा फ़ाइल के साथ प्रिज्मा अधिक सारगर्भित है। जटिल प्रश्नों में ड्रिज़ल का टाइपस्क्रिप्ट अनुमान बेहतर है, रनटाइम ओवरहेड छोटा है, और इसके लिए कोड जनरेशन चरण की आवश्यकता नहीं होती है। प्रिज्मा में एक बड़ा पारिस्थितिकी तंत्र और सॉफ्ट डिलीट और ऑडिट लॉगिंग जैसी अधिक अंतर्निहित सुविधाएं हैं। एसक्यूएल के साथ सहज बड़ी टीमों के लिए, ड्रिज़ल को आम तौर पर प्राथमिकता दी जाती है।
क्या मुझे ड्रिज़ल-किट पुश का उपयोग करना चाहिए या सीआई/सीडी में माइग्रेट करना चाहिए?
CI/CD में हमेशा drizzle-kit migrate का प्रयोग करें। push कमांड तेजी से स्थानीय विकास के लिए है - यह माइग्रेशन फ़ाइलें बनाए बिना सीधे स्कीमा परिवर्तन लागू करता है, इसलिए कोई ऑडिट ट्रेल नहीं है और रोलबैक मुश्किल है। अपनी परिनियोजन पाइपलाइन में, स्कीमा सिंक में है यह सुनिश्चित करने के लिए एप्लिकेशन शुरू करने से पहले drizzle-kit migrate चलाएँ।
मैं ड्रिज़ल के साथ डेटाबेस सीडिंग को कैसे संभालूं?
एक अलग बीज फ़ाइल बनाएं और अपना स्कीमा सीधे आयात करें। निष्क्रिय बीजों के लिए onConflictDoNothing() का उपयोग करें जो दोबारा चलाने पर विफल नहीं होंगे। आसान अपडेट के लिए बीज डेटा को स्थिरांक में संग्रहीत करें, और अपनी डेव सेटअप स्क्रिप्ट में माइग्रेशन के बाद बीज चलाएं।
मैं ड्रिज़ल और पोस्टग्रेएसक्यूएल के साथ पूर्ण-पाठ खोज कैसे करूं?
PostgreSQL के to_tsvector और to_tsquery फ़ंक्शंस के साथ sql टेम्पलेट शाब्दिक का उपयोग करें। ड्रिज़ल पूर्ण-पाठ खोज को अमूर्त नहीं करता है, लेकिन आप इसे सीधे लिख सकते हैं। प्रदर्शन के लिए tsvector कॉलम पर एक GIN इंडेक्स जोड़ें, और प्राकृतिक भाषा प्रश्नों को सुरक्षित रूप से पार्स करने के लिए उपयोगकर्ता द्वारा प्रदत्त खोज शब्दों के लिए websearch_to_tsquery का उपयोग करें।
मैं बिना डाउनटाइम के स्कीमा परिवर्तनों को कैसे प्रबंधित करूं?
एडिटिव माइग्रेशन का उपयोग करें - पहले कॉलम को शून्य के रूप में जोड़ें, डेटा को बैकफ़िल करें, फिर NOT NULL बाधा जोड़ें। कभी भी कॉलम को उसी माइग्रेशन में न छोड़ें जो उन्हें कोड से हटा देता है। अनुक्रम है: 1) पुराने कॉलम को अनदेखा करने वाले कोड को तैनात करें, 2) इसे छोड़ने के लिए माइग्रेशन को तैनात करें। यह सुनिश्चित करता है कि डेटा हानि के बिना रोलबैक हमेशा संभव है।
अगले चरण
एक अच्छी तरह से डिज़ाइन की गई डेटाबेस परत वह नींव है जिस पर प्रत्येक उत्पादन एप्लिकेशन बनाया जाता है। ECOSIRE की इंजीनियरिंग टीम प्रतिदिन ड्रिज़ल ORM और PostgreSQL के साथ काम करती है, एक जटिल मल्टी-टेनेंट सिस्टम में 65+ स्कीमा फ़ाइलों का प्रबंधन करती है।
चाहे आपको डेटाबेस आर्किटेक्चर परामर्श, ओडू ईआरपी एकीकरण, या आधुनिक टाइपस्क्रिप्ट टूलिंग के साथ निर्मित पूर्ण बैकएंड सिस्टम की आवश्यकता हो, यह जानने के लिए कि हम कैसे मदद कर सकते हैं, हमारी सेवाओं का पता लगाएं।
लेखक
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
संबंधित लेख
Zero-Downtime Database Migrations with Drizzle ORM
Run database migrations without downtime using Drizzle ORM. Covers expand-contract pattern, backward-compatible schema changes, rollback strategies, and CI/CD integration for PostgreSQL.
Odoo Performance Tuning: PostgreSQL and Server Optimization
Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.
Natural Language Database Queries with OpenClaw
How OpenClaw enables natural language database queries, translating plain English business questions into accurate SQL without exposing database credentials or query complexity.