# Prisma Database Patterns - Detailed Reference

This reference contains detailed code examples for Prisma ORM and database patterns.

## Prisma Schema

```prisma
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

// Base model with common fields
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  password  String
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  posts     Post[]
  profile   Profile?

  // Indexes for performance
  @@index([email])
  @@index([createdAt])
  @@map("users")
}

model Post {
  id          String    @id @default(cuid())
  title       String
  content     String?
  published   Boolean   @default(false)
  authorId    String
  author      User      @relation(fields: [authorId], references: [id])
  createdAt   DateTime  @default(now())
  updatedAt   DateTime  @updatedAt

  @@index([authorId])
  @@index([published, createdAt])
  @@map("posts")
}

model Profile {
  id        String   @id @default(cuid())
  bio       String?
  avatar    String?
  userId    String   @unique
  user      User     @relation(fields: [userId], references: [id])

  @@map("profiles")
}

enum Role {
  USER
  ADMIN
  MODERATOR
}
```

## Database Connection

```typescript
// src/lib/prisma.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = global 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;
}

// Middleware for soft delete
prisma.$use(async (params, next) => {
  if (params.model && params.action === 'delete') {
    return next({
      ...params,
      action: 'update',
      args: {
        ...params.args,
        data: { deletedAt: new Date() }
      }
    });
  }
  return next(params);
});
```

## Repository Pattern

```typescript
// src/repositories/base.repository.ts
export abstract class BaseRepository<T> {
  constructor(protected prisma: PrismaClient) {}

  abstract findById(id: string): Promise<T | null>;
  abstract findAll(options?: any): Promise<T[]>;
  abstract create(data: any): Promise<T>;
  abstract update(id: string, data: any): Promise<T>;
  abstract delete(id: string): Promise<void>;
}

// src/repositories/user.repository.ts
export class UserRepository extends BaseRepository<User> {
  async findById(id: string): Promise<User | null> {
    return this.prisma.user.findUnique({
      where: { id },
      include: { profile: true }
    });
  }

  async findByEmail(email: string): Promise<User | null> {
    return this.prisma.user.findUnique({
      where: { email }
    });
  }

  async findAll(options: {
    page?: number;
    limit?: number;
    search?: string;
    sortBy?: string;
    order?: 'asc' | 'desc';
  } = {}): Promise<{ data: User[]; total: number }> {
    const {
      page = 1,
      limit = 10,
      search,
      sortBy = 'createdAt',
      order = 'desc'
    } = options;

    const where = search ? {
      OR: [
        { email: { contains: search, mode: 'insensitive' } },
        { name: { contains: search, mode: 'insensitive' } }
      ]
    } : {};

    const [data, total] = await Promise.all([
      this.prisma.user.findMany({
        where,
        skip: (page - 1) * limit,
        take: limit,
        orderBy: { [sortBy]: order },
        include: { profile: true }
      }),
      this.prisma.user.count({ where })
    ]);

    return { data, total };
  }

  async create(data: CreateUserDto): Promise<User> {
    return this.prisma.user.create({
      data: {
        email: data.email,
        password: data.password,
        name: data.name,
        profile: data.bio ? {
          create: { bio: data.bio }
        } : undefined
      },
      include: { profile: true }
    });
  }

  async update(id: string, data: UpdateUserDto): Promise<User> {
    return this.prisma.user.update({
      where: { id },
      data,
      include: { profile: true }
    });
  }

  async delete(id: string): Promise<void> {
    await this.prisma.user.delete({
      where: { id }
    });
  }
}
```

## Transactions

```typescript
// Transaction example
export class TransferService {
  async transferFunds(
    fromAccountId: string,
    toAccountId: string,
    amount: number
  ) {
    return await this.prisma.$transaction(async (tx) => {
      // Check balance
      const fromAccount = await tx.account.findUnique({
        where: { id: fromAccountId }
      });

      if (!fromAccount || fromAccount.balance < amount) {
        throw new Error('Insufficient funds');
      }

      // Deduct from sender
      const updatedFrom = await tx.account.update({
        where: { id: fromAccountId },
        data: { balance: { decrement: amount } }
      });

      // Add to receiver
      const updatedTo = await tx.account.update({
        where: { id: toAccountId },
        data: { balance: { increment: amount } }
      });

      // Create transaction record
      const transaction = await tx.transaction.create({
        data: {
          fromAccountId,
          toAccountId,
          amount,
          type: 'TRANSFER',
          status: 'COMPLETED'
        }
      });

      return transaction;
    }, {
      maxWait: 5000,
      timeout: 10000,
    });
  }
}
```

## Query Optimization

```typescript
// Optimized queries
export class OptimizedUserRepository {
  // Select only needed fields
  async findUsersLight() {
    return this.prisma.user.findMany({
      select: {
        id: true,
        email: true,
        name: true
      }
    });
  }

  // Use pagination
  async findPaginated(cursor?: string) {
    return this.prisma.user.findMany({
      take: 10,
      skip: cursor ? 1 : 0,
      cursor: cursor ? { id: cursor } : undefined,
      orderBy: { createdAt: 'desc' }
    });
  }

  // Batch operations
  async createMany(users: CreateUserDto[]) {
    return this.prisma.user.createMany({
      data: users,
      skipDuplicates: true
    });
  }

  // Use raw SQL for complex queries
  async getStatistics() {
    return this.prisma.$queryRaw`
      SELECT
        COUNT(*) as total,
        COUNT(CASE WHEN role = 'ADMIN' THEN 1 END) as admins,
        COUNT(CASE WHEN created_at > NOW() - INTERVAL '30 days' THEN 1 END) as new_users
      FROM users
    `;
  }
}
```

## Seeding

```typescript
// prisma/seed.ts
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcrypt';

const prisma = new PrismaClient();

async function main() {
  // Create admin user
  const adminPassword = await bcrypt.hash('admin123', 10);
  const admin = await prisma.user.upsert({
    where: { email: 'admin@goodgo.com' },
    update: {},
    create: {
      email: 'admin@goodgo.com',
      name: 'Admin User',
      password: adminPassword,
      role: 'ADMIN'
    }
  });

  // Create test users
  const testUsers = Array.from({ length: 10 }, (_, i) => ({
    email: `user${i}@example.com`,
    name: `Test User ${i}`,
    password: bcrypt.hashSync('password123', 10)
  }));

  await prisma.user.createMany({
    data: testUsers,
    skipDuplicates: true
  });

  console.log('Database seeded successfully');
}

main()
  .catch(console.error)
  .finally(() => prisma.$disconnect());
```

## Testing with Prisma

```typescript
// __tests__/user.repository.test.ts
import { mockDeep, mockReset } from 'jest-mock-extended';
import { PrismaClient } from '@prisma/client';

jest.mock('../src/lib/prisma', () => ({
  __esModule: true,
  prisma: mockDeep<PrismaClient>()
}));

describe('UserRepository', () => {
  beforeEach(() => {
    mockReset(prismaMock);
  });

  it('should create user', async () => {
    const user = { id: '1', email: 'test@example.com' };
    prismaMock.user.create.mockResolvedValue(user);

    const result = await repository.create({
      email: 'test@example.com',
      password: 'password'
    });

    expect(result).toEqual(user);
  });
});
```

## Migration Files

```sql
-- migrations/20240101000000_add_user_table/migration.sql
CREATE TABLE "users" (
    "id" TEXT NOT NULL,
    "email" TEXT NOT NULL,
    "name" TEXT,
    "password" TEXT NOT NULL,
    "role" TEXT NOT NULL DEFAULT 'USER',
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" TIMESTAMP(3) NOT NULL,

    CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);

CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
CREATE INDEX "users_createdAt_idx" ON "users"("createdAt");
```

## Neon PostgreSQL Configuration

```typescript
// .env
DATABASE_URL="postgresql://user:password@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require"

// Connection pooling for serverless
DIRECT_URL="postgresql://user:password@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require"
```
