PostgreSQL & Prisma ORM
📖 Concept
PostgreSQL is the most advanced open-source relational database, and Prisma is a modern, type-safe ORM for Node.js/TypeScript that replaces traditional ORMs like Sequelize.
Why PostgreSQL?
- ACID transactions for data integrity
- Complex joins, aggregations, and window functions
- JSON/JSONB columns for flexible data
- Full-text search built-in
- Time-tested reliability at scale
Why Prisma over Sequelize/TypeORM?
| Feature | Prisma | Sequelize |
|---|---|---|
| Schema definition | .prisma file (declarative) |
JavaScript models |
| Type safety | Auto-generated TypeScript types | Manual typing |
| Migrations | Auto-generated, versioned | Manual or auto |
| Query API | Intuitive, chainable | String-based in complex queries |
| Relations | Declarative in schema | Configured in models |
| Learning curve | Low | Medium-high |
Prisma workflow:
- Define schema in
schema.prisma - Run
npx prisma migrate devto create/update database tables - Run
npx prisma generateto generate the TypeScript client - Use
PrismaClientin your app for type-safe queries
🏠 Real-world analogy: If MongoDB is a filing cabinet (flexible, documents), PostgreSQL is a spreadsheet application (structured, relational). Prisma is the smart assistant who writes your SQL queries for you and ensures every formula (type) is correct.
💻 Code Example
1// PostgreSQL with Prisma — Complete Guide23// === schema.prisma ===4// datasource db {5// provider = "postgresql"6// url = env("DATABASE_URL")7// }8//9// generator client {10// provider = "prisma-client-js"11// }12//13// model User {14// id Int @id @default(autoincrement())15// email String @unique16// name String17// password String18// role Role @default(USER)19// posts Post[]20// profile Profile?21// createdAt DateTime @default(now())22// updatedAt DateTime @updatedAt23//24// @@index([email])25// @@map("users")26// }27//28// model Post {29// id Int @id @default(autoincrement())30// title String31// content String?32// published Boolean @default(false)33// author User @relation(fields: [authorId], references: [id])34// authorId Int35// tags Tag[]36// createdAt DateTime @default(now())37//38// @@index([authorId])39// @@map("posts")40// }41//42// model Profile {43// id Int @id @default(autoincrement())44// bio String?45// avatar String?46// user User @relation(fields: [userId], references: [id])47// userId Int @unique48// }49//50// model Tag {51// id Int @id @default(autoincrement())52// name String @unique53// posts Post[]54// }55//56// enum Role {57// USER58// ADMIN59// MODERATOR60// }6162// === src/db.js — Prisma Client Singleton ===63const { PrismaClient } = require("@prisma/client");6465const prisma = new PrismaClient({66 log: process.env.NODE_ENV === "development" ? ["query", "warn", "error"] : ["error"],67});6869// Graceful shutdown70process.on("beforeExit", async () => {71 await prisma.$disconnect();72});7374module.exports = prisma;7576// === Usage: CRUD Operations ===77const prisma2 = require("./db");7879async function prismaExamples() {80 // CREATE81 const user = await prisma2.user.create({82 data: {83 name: "Alice",84 email: "alice@example.com",85 password: "hashed_password",86 profile: {87 create: { bio: "Node.js developer" },88 },89 },90 include: { profile: true },91 });9293 // READ with relations94 const userWithPosts = await prisma2.user.findUnique({95 where: { id: 1 },96 include: {97 posts: { where: { published: true }, orderBy: { createdAt: "desc" } },98 profile: true,99 },100 });101102 // Pagination103 const users = await prisma2.user.findMany({104 where: { role: "USER" },105 skip: 0,106 take: 10,107 orderBy: { createdAt: "desc" },108 select: { id: true, name: true, email: true },109 });110111 // UPDATE112 const updated = await prisma2.user.update({113 where: { id: 1 },114 data: { name: "Alice Updated" },115 });116117 // DELETE118 await prisma2.user.delete({ where: { id: 1 } });119120 // Transaction121 const [newUser, newPost] = await prisma2.$transaction([122 prisma2.user.create({ data: { name: "Bob", email: "bob@example.com", password: "hash" } }),123 prisma2.post.create({ data: { title: "Hello", content: "World", authorId: 1 } }),124 ]);125126 // Aggregation127 const stats = await prisma2.user.groupBy({128 by: ["role"],129 _count: { _all: true },130 _avg: { id: true },131 });132133 // Raw SQL (escape hatch)134 const result = await prisma2.$queryRaw`135 SELECT u.name, COUNT(p.id) as post_count136 FROM users u137 LEFT JOIN posts p ON p."authorId" = u.id138 GROUP BY u.name139 ORDER BY post_count DESC140 `;141}
🏋️ Practice Exercise
Exercises:
- Create a Prisma schema for an e-commerce app with Users, Products, Orders, and OrderItems
- Implement CRUD operations for all models with proper relation handling
- Write a transaction that creates an order and updates product stock atomically
- Set up Prisma migrations — create, modify, and roll back schema changes
- Implement cursor-based pagination for a high-volume posts endpoint
- Compare query performance: Prisma generated queries vs raw SQL for complex aggregations
⚠️ Common Mistakes
Not creating a Prisma Client singleton — instantiating
new PrismaClient()per request exhausts database connectionsForgetting to call
prisma.$disconnect()on shutdown — open connections prevent the process from exiting cleanlyUsing N+1 queries — fetching users then looping to fetch each user's posts; use
includefor eager loadingNot running
prisma generateafter schema changes — the client won't have the updated types until regeneratedIgnoring migration drift — always use
prisma migrate devin development andprisma migrate deployin production
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for PostgreSQL & Prisma ORM. Login to unlock this feature.