# Database Patterns - Prisma Best Practices Complete guide to database access patterns using Prisma in backend microservices. ## Table of Contents - [PrismaService Usage](#prismaservice-usage) - [Repository Pattern](#repository-pattern) - [Transaction Patterns](#transaction-patterns) - [Query Optimization](#query-optimization) - [N+1 Query Prevention](#n1-query-prevention) - [Error Handling](#error-handling) --- ## PrismaService Usage ### Basic Pattern ```typescript import { PrismaService } from '@project-lifecycle-portal/database'; // Always use PrismaService.main const users = await PrismaService.main.user.findMany(); ``` ### Check Availability ```typescript if (!PrismaService.isAvailable) { throw new Error('Prisma client not initialized'); } const user = await PrismaService.main.user.findUnique({ where: { id } }); ``` --- ## Repository Pattern ### Why Use Repositories ✅ **Use repositories when:** - Complex queries with joins/includes - Query used in multiple places - Need caching layer - Want to mock for testing ❌ **Skip repositories for:** - Simple one-off queries - Prototyping (can refactor later) ### Repository Template ```typescript export class UserRepository { async findById(id: string): Promise { return PrismaService.main.user.findUnique({ where: { id }, include: { profile: true }, }); } async findActive(): Promise { return PrismaService.main.user.findMany({ where: { isActive: true }, orderBy: { createdAt: 'desc' }, }); } async create(data: Prisma.UserCreateInput): Promise { return PrismaService.main.user.create({ data }); } } ``` --- ## Transaction Patterns ### Simple Transaction ```typescript const result = await PrismaService.main.$transaction(async (tx) => { const user = await tx.user.create({ data: userData }); const profile = await tx.userProfile.create({ data: { userId: user.id } }); return { user, profile }; }); ``` ### Interactive Transaction ```typescript const result = await PrismaService.main.$transaction( async (tx) => { const user = await tx.user.findUnique({ where: { id } }); if (!user) throw new Error('User not found'); return await tx.user.update({ where: { id }, data: { lastLogin: new Date() }, }); }, { maxWait: 5000, timeout: 10000, } ); ``` --- ## Query Optimization ### Use select to Limit Fields ```typescript // ❌ Fetches all fields const users = await PrismaService.main.user.findMany(); // ✅ Only fetch needed fields const users = await PrismaService.main.user.findMany({ select: { id: true, email: true, profile: { select: { firstName: true, lastName: true } }, }, }); ``` ### Use include Carefully ```typescript // ❌ Excessive includes const user = await PrismaService.main.user.findUnique({ where: { id }, include: { profile: true, posts: { include: { comments: true } }, workflows: { include: { steps: { include: { actions: true } } } }, }, }); // ✅ Only include what you need const user = await PrismaService.main.user.findUnique({ where: { id }, include: { profile: true }, }); ``` --- ## N+1 Query Prevention ### Problem: N+1 Queries ```typescript // ❌ N+1 Query Problem const users = await PrismaService.main.user.findMany(); // 1 query for (const user of users) { // N queries (one per user) const profile = await PrismaService.main.userProfile.findUnique({ where: { userId: user.id }, }); } ``` ### Solution: Use include or Batching ```typescript // ✅ Single query with include const users = await PrismaService.main.user.findMany({ include: { profile: true }, }); // ✅ Or batch query const userIds = users.map(u => u.id); const profiles = await PrismaService.main.userProfile.findMany({ where: { userId: { in: userIds } }, }); ``` --- ## Error Handling ### Prisma Error Types ```typescript import { Prisma } from '@prisma/client'; try { await PrismaService.main.user.create({ data }); } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError) { // Unique constraint violation if (error.code === 'P2002') { throw new ConflictError('Email already exists'); } // Foreign key constraint if (error.code === 'P2003') { throw new ValidationError('Invalid reference'); } // Record not found if (error.code === 'P2025') { throw new NotFoundError('Record not found'); } } // Unknown error Sentry.captureException(error); throw error; } ``` --- **Related Files:** - [SKILL.md](SKILL.md) - [services-and-repositories.md](services-and-repositories.md) - [async-and-errors.md](async-and-errors.md)