Developing with Prisma
Quick Start
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
posts Post[]
createdAt DateTime @default(now())
@@index([email])
}
model Post {
id String @id @default(cuid())
title String
authorId String
author User @relation(fields: [authorId], references: [id])
}
npx prisma migrate dev --name init
npx prisma generate
Features
| Feature |
Description |
Guide |
| Schema Design |
Declarative data modeling with relations |
Define models, relations, indexes in schema.prisma |
| Type-Safe Queries |
Auto-generated TypeScript types |
Use findMany, findUnique, create, update |
| Migrations |
Version-controlled schema changes |
prisma migrate dev for development, deploy for production |
| Relations |
One-to-one, one-to-many, many-to-many |
Use include or select to load related data |
| Transactions |
ACID operations across multiple queries |
Use $transaction for atomic operations |
| Raw Queries |
Execute raw SQL when needed |
Use $queryRaw for complex queries |
Common Patterns
Repository with Pagination
async function findUsers(page = 1, limit = 20, where?: Prisma.UserWhereInput) {
const [data, total] = await prisma.$transaction([
prisma.user.findMany({ where, skip: (page - 1) * limit, take: limit, include: { profile: true } }),
prisma.user.count({ where }),
]);
return { data, pagination: { page, limit, total, totalPages: Math.ceil(total / limit) } };
}
Interactive Transaction
async function createOrder(userId: string, items: { productId: string; qty: number }[]) {
return prisma.$transaction(async (tx) => {
let total = 0;
for (const item of items) {
const product = await tx.product.update({
where: { id: item.productId },
data: { stock: { decrement: item.qty } },
});
if (product.stock < 0) throw new Error(`Insufficient stock: ${product.name}`);
total += product.price * item.qty;
}
return tx.order.create({ data: { userId, total, items: { create: items } } });
});
}
Cursor-Based Pagination
async function getPaginatedPosts(cursor?: string, take = 20) {
const posts = await prisma.post.findMany({
take: take + 1,
...(cursor && { skip: 1, cursor: { id: cursor } }),
orderBy: { createdAt: 'desc' },
});
const hasMore = posts.length > take;
return { data: hasMore ? posts.slice(0, -1) : posts, nextCursor: hasMore ? posts[take - 1].id : null };
}
Best Practices
| Do |
Avoid |
Use select to fetch only needed fields |
Exposing Prisma Client directly in APIs |
| Create indexes for frequently queried fields |
Skipping migrations in production |
| Use transactions for multi-table operations |
Ignoring N+1 query problems |
| Run migrations in CI/CD pipelines |
Hardcoding connection strings |
| Use connection pooling in production |
Using raw queries unless necessary |
| Validate input before database operations |
Using implicit many-to-many for complex joins |
| Seed development databases consistently |
Ignoring transaction isolation levels |