185 lines
5.3 KiB
Markdown
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
|