Concept
Schema-First Declarative Data Modeling
Unlike legacy ORMs where models are configured in JavaScript classes, Prisma uses a single declarative file: schema.prisma. It acts as the single source of truth for the database schema:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
authorId Int
author User @relation(fields: [authorId], references: [id])
}Running npx prisma generate reads this schema and compiles a fully typed client custom-tailored to your exact data model, providing IDE autocomplete and compile-time type safety.
Querying with Prisma Client
Prisma automatically translates TypeScript calls to optimized SQL:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Queries user and joins posts
const users = await prisma.user.findMany({
where: { email: { endsWith: '@example.com' } },
include: { posts: true }
});Prisma handles relationships by executing nested parameter queries, ensuring joins are type-safe.
Common Mistakes
1. Incurring N+1 query loops using loop-level lookups
If you retrieve users and then run a separate prisma.post.findMany() inside a .map() loop for each user, you execute N+1 database roundtrips. Always load relationships using the include parameters:
// ❌ WRONG: N+1 database hits
const users = await prisma.user.findMany();
const usersWithPosts = await Promise.all(users.map(async (user) => {
const posts = await prisma.post.findMany({ where: { authorId: user.id } });
return { ...user, posts };
}));
// CORRECT: 1-2 database hits total
const usersWithPosts = await prisma.user.findMany({
include: { posts: true }
});2. Forgetting to regenerate the client after schema changes
If you modify schema.prisma and deploy without running prisma generate, the type files and client methods drift from the database, leading to runtime query crashes.
Best Practices
- Log Queries in Dev: Always initialize Prisma Client with query logging enabled during development to monitor the SQL queries it emits:
const prisma = new PrismaClient({ log: ['query', 'error'] }); - Leverage Transaction Batching: Execute concurrent writes using
prisma.$transaction([ ... ])to guarantee ACID compliance and speed up performance. - Use Prisma Migrate: Manage database updates using
npx prisma migrate devto generate structured SQL version files automatically.
