Prisma ORM patterns - use for database access in Next.js, schema design, migrations, transactions, and relations
Prisma ORM Patterns
Schema Definition
// 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
name String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
environments Environment[]
sessions Session[]
@@map("users")
}
model Environment {
id String @id @default(cuid())
name String
description String?
status EnvironmentStatus @default(PENDING)
ownerId String @map("owner_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
tags Tag[]
@@unique([ownerId, name])
@@index([status])
@@index([createdAt(sort: Desc)])
@@map("environments")
}
model Tag {
id String @id @default(cuid())
key String
value String
environmentId String @map("environment_id")
createdAt DateTime @default(now()) @map("created_at")
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
@@unique([environmentId, key])
@@map("tags")
}
enum EnvironmentStatus {
PENDING
RUNNING
STOPPED
FAILED
}
Prisma Client Setup
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
log: process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
})
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
Basic CRUD Operations
// Create
const environment = await prisma.environment.create({
data: {
name: 'dev-env',
description: 'Development environment',
ownerId: userId,
},
})
// Read
const environment = await prisma.environment.findUnique({
where: { id: envId },
})
const environments = await prisma.environment.findMany({
where: { status: 'RUNNING' },
orderBy: { createdAt: 'desc' },
take: 10,
})
// Update
const updated = await prisma.environment.update({
where: { id: envId },
data: { status: 'STOPPED' },
})
// Delete
await prisma.environment.delete({
where: { id: envId },
})
// Upsert
const env = await prisma.environment.upsert({
where: { id: envId },
update: { status: 'RUNNING' },
create: {
name: 'new-env',
ownerId: userId,
},
})
Relations
// Include relations
const envWithOwner = await prisma.environment.findUnique({
where: { id: envId },
include: {
owner: true,
tags: true,
},
})
// Select specific fields
const envPartial = await prisma.environment.findUnique({
where: { id: envId },
select: {
id: true,
name: true,
owner: {
select: {
name: true,
email: true,
},
},
},
})
// Nested create
const envWithTags = await prisma.environment.create({
data: {
name: 'tagged-env',
ownerId: userId,
tags: {
create: [
{ key: 'team', value: 'platform' },
{ key: 'tier', value: 'production' },
],
},
},
include: { tags: true },
})
// Connect existing relation
const env = await prisma.environment.create({
data: {
name: 'new-env',
owner: {
connect: { id: userId },
},
},
})
Filtering
// Complex filters
const environments = await prisma.environment.findMany({
where: {
AND: [
{ status: 'RUNNING' },
{
OR: [
{ name: { contains: 'prod', mode: 'insensitive' } },
{ tags: { some: { key: 'tier', value: 'production' } } },
],
},
],
createdAt: {
gte: new Date('2024-01-01'),
},
owner: {
email: { endsWith: '@jetbrains.com' },
},
},
})
// NOT filter
const nonFailedEnvs = await prisma.environment.findMany({
where: {
NOT: { status: 'FAILED' },
},
})
Pagination
// Offset pagination
async function getEnvironmentsPage(page: number, pageSize: number) {
const [items, total] = await Promise.all([
prisma.environment.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' },
}),
prisma.environment.count(),
])
return {
items,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
}
}
// Cursor pagination
async function getEnvironmentsCursor(cursor?: string, take: number = 10) {
const items = await prisma.environment.findMany({
take: take + 1, // Fetch one extra to check if there's more
...(cursor && {
cursor: { id: cursor },
skip: 1, // Skip the cursor
}),
orderBy: { createdAt: 'desc' },
})
const hasMore = items.length > take
const data = hasMore ? items.slice(0, -1) : items
return {
items: data,
nextCursor: hasMore ? data[data.length - 1].id : null,
}
}
Transactions
// Interactive transaction
const result = await prisma.$transaction(async (tx) => {
// Create environment
const env = await tx.environment.create({
data: { name: 'new-env', ownerId: userId },
})
// Create associated tags
await tx.tag.createMany({
data: [
{ key: 'team', value: 'platform', environmentId: env.id },
{ key: 'cost-center', value: '12345', environmentId: env.id },
],
})
// Update user's environment count (if tracking)
await tx.user.update({
where: { id: userId },
data: { environmentCount: { increment: 1 } },
})
return env
})
// Sequential transaction (batch)
const [deletedEnvs, deletedTags] = await prisma.$transaction([
prisma.environment.deleteMany({ where: { status: 'FAILED' } }),
prisma.tag.deleteMany({ where: { environment: { status: 'FAILED' } } }),
])
Aggregations
// Count by status
const statusCounts = await prisma.environment.groupBy({
by: ['status'],
_count: { status: true },
})
// Aggregate functions
const stats = await prisma.environment.aggregate({
_count: { id: true },
_min: { createdAt: true },
_max: { createdAt: true },
})
Raw Queries (When Needed)
// Raw query
const result = await prisma.$queryRaw<Environment[]>`
SELECT * FROM environments
WHERE status = ${status}
AND created_at > NOW() - INTERVAL '7 days'
`
// Raw execute
await prisma.$executeRaw`
UPDATE environments
SET status = 'STOPPED'
WHERE status = 'RUNNING'
AND updated_at < NOW() - INTERVAL '24 hours'
`
Migrations
# Create migration
npx prisma migrate dev --name add_environment_type
# Apply migrations (production)
npx prisma migrate deploy
# Reset database (development)
npx prisma migrate reset
# Generate client
npx prisma generate
You Might Also Like
Related Skills

zig-system-calls
Guides using bun.sys for system calls and file I/O in Zig. Use when implementing file operations instead of std.fs or std.posix.
oven-sh
bun-file-io
Use this when you are working on file operations like reading, writing, scanning, or deleting files. It summarizes the preferred file APIs and patterns used in this repo. It also notes when to use filesystem helpers for directories.
anomalyco
vector-index-tuning
Optimize vector index performance for latency, recall, and memory. Use when tuning HNSW parameters, selecting quantization strategies, or scaling vector search infrastructure.
wshobson
similarity-search-patterns
Implement efficient similarity search with vector databases. Use when building semantic search, implementing nearest neighbor queries, or optimizing retrieval performance.
wshobson
dbt-transformation-patterns
Master dbt (data build tool) for analytics engineering with model organization, testing, documentation, and incremental strategies. Use when building data transformations, creating data models, or implementing analytics engineering best practices.
wshobson
event-store-design
Design and implement event stores for event-sourced systems. Use when building event sourcing infrastructure, choosing event store technologies, or implementing event persistence patterns.
wshobson