Files
pos-system/.agent/rules/database-prisma.md

185 lines
5.3 KiB
Markdown

---
trigger: always_on
---
# Prisma Database Patterns
## When to Use This Skill
Use this skill when:
- Setting up Prisma for a new service
- Creating or modifying database schemas
- Writing database migrations
- Implementing repository patterns
- Optimizing database queries
- Setting up database connections
- Implementing transactions
- Working with Neon PostgreSQL
## Core Concepts
### Architecture
- Repository pattern for data access
- Prisma as ORM for type safety
- Neon PostgreSQL as primary database
- Connection pooling for performance
- Transaction support for data consistency
## Key Patterns
### Prisma Setup
```bash
npm install @prisma/client prisma
npx prisma init
```
### Schema Definition
```prisma
model User {
id String @id @default(cuid())
email String @unique
name String?
role Role @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
@@index([email])
@@index([createdAt])
@@map("users")
}
```
### Database Connection
```typescript
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'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
```
### Repository Pattern
```typescript
export class UserRepository {
async findById(id: string) {
return prisma.user.findUnique({ where: { id }, include: { profile: true } });
}
async findAll({ page = 1, limit = 10, search }: QueryOptions) {
const where = search ? { OR: [
{ email: { contains: search, mode: 'insensitive' } },
{ name: { contains: search, mode: 'insensitive' } }
]} : {};
const [data, total] = await Promise.all([
prisma.user.findMany({ where, skip: (page - 1) * limit, take: limit }),
prisma.user.count({ where })
]);
return { data, total };
}
async create(data: CreateUserDto) {
return prisma.user.create({ data });
}
}
```
### Transactions
```typescript
await prisma.$transaction(async (tx) => {
await tx.account.update({ where: { id: from }, data: { balance: { decrement: amount } } });
await tx.account.update({ where: { id: to }, data: { balance: { increment: amount } } });
}, { maxWait: 5000, timeout: 10000 });
```
## Best Practices
- **Schema Design**: Use appropriate field types, add indexes for frequently queried fields
- **Performance**: Use select to fetch only needed fields, implement pagination
- **Security**: Never expose sensitive fields, use parameterized queries
- **Maintenance**: Keep migrations small and focused, test before production
## Common Mistakes
1. **N+1 Query Problem**: Fetching related data in a loop
```typescript
// BAD: N+1 queries
for (const user of users) { await prisma.post.findMany({ where: { authorId: user.id } }); }
// GOOD: Include relations
await prisma.user.findMany({ include: { posts: true } });
```
2. **No Indexes**: Missing indexes on frequently queried columns
```prisma
// GOOD: Add index
@@index([createdAt])
```
3. **Raw Queries Without Parameters**: SQL injection risk
```typescript
// BAD: prisma.$queryRawUnsafe(`SELECT * FROM users WHERE email = '${email}'`);
// GOOD: prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`;
```
4. **Not Using Transactions**: Data inconsistency risk
```typescript
// GOOD: Use transaction for related operations
await prisma.$transaction([update1, update2]);
```
5. **Exposing Internal IDs**: Leaking database structure
```prisma
// GOOD: Use CUID or UUID
model User { id String @id @default(cuid()) }
```
## Quick Reference
| Operation | Command |
|-----------|---------|
| **Create migration** | `npx prisma migrate dev --name <name>` |
| **Apply migrations** | `npx prisma migrate deploy` |
| **Reset database** | `npx prisma migrate reset` |
| **Generate client** | `npx prisma generate` |
| **Open Studio** | `npx prisma studio` |
| **Seed database** | `npx prisma db seed` |
**Common Query Patterns:**
```typescript
await prisma.user.findUnique({ where: { id } }); // Find unique
await prisma.user.findMany({ include: { posts: true } }); // With relations
await prisma.user.findMany({ select: { id: true } }); // Select fields
await prisma.user.findMany({ skip: 10, take: 10 }); // Pagination
await prisma.$transaction(async (tx) => { ... }); // Transaction
```
**Schema Field Types:**
| Type | PostgreSQL | Description |
|------|------------|-------------|
| `String` | TEXT | Variable-length string |
| `Int` | INTEGER | 32-bit integer |
| `BigInt` | BIGINT | 64-bit integer |
| `Float` | DOUBLE PRECISION | Floating point |
| `Boolean` | BOOLEAN | True/false |
| `DateTime` | TIMESTAMP | Date and time |
| `Json` | JSONB | JSON data |
## Resources
- [Prisma Documentation](https://www.prisma.io/docs) - Official Prisma docs
- [Prisma Schema Reference](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference)
- [Detailed Code Examples](./references/REFERENCE.md)
- [Repository Pattern](../repository-pattern/SKILL.md) - Data access patterns
- [Caching Patterns](../caching-patterns/SKILL.md) - Query caching
- [Testing Patterns](../testing-patterns/SKILL.md) - Database mocking