Using the Prisma Client
Learn how to use the Prisma client for type-safe database operations in MakerKit.
Import the Prisma client as db from @kit/database to perform type-safe database operations. The client provides findMany, findUnique, create, update, and delete methods for all models defined in your schema, with full TypeScript autocomplete.
This guide is part of the Database Configuration documentation.
Using the Prisma Client
Perform type-safe database operations.
Basic Operations
Import the client from @kit/database:
import { db } from '@kit/database';CRUD Operations
import { db } from '@kit/database';import { generateId } from '@kit/shared/utils';// Find all usersconst allUsers = await db.user.findMany();// Create a new organizationconst newOrganization = await db.organization.create({ data: { id: generateId(), name: 'Acme Corp', slug: 'acme-corp', createdAt: new Date(), },});// Find user by emailconst user = await db.user.findUnique({ where: { email: 'alice@acme-corp.com' },});// Update a userawait db.user.update({ where: { id: userId }, data: { name: 'Alice Johnson' },});// Delete a userawait db.user.delete({ where: { id: userId },});Tenant-Scoped Queries
Always filter by organizationId for tenant-scoped data to ensure proper isolation:
Never query tenant-scoped tables without filtering by organizationId. Omitting this filter leaks data across tenants - a critical security vulnerability.
// Correct - respects multi-tenancyconst projects = await db.project.findMany({ where: { organizationId: currentOrgId }, orderBy: { createdAt: 'desc' }, take: 20,});// DANGER - leaks data across tenantsconst allProjects = await db.project.findMany(); // Never do thisFor queries that need user context:
// Find all organizations a user belongs toconst memberships = await db.member.findMany({ where: { userId: currentUserId }, include: { organization: true },});const organizations = memberships.map((m) => m.organization);Relations and Includes
Use include to fetch related data in a single query:
// Fetch organization with members and their user profilesconst orgWithMembers = await db.organization.findUnique({ where: { id: orgId }, include: { members: { include: { user: true }, }, },});// Access nested dataorgWithMembers?.members.forEach((member) => { console.log(member.user.email, member.role);});Use select for performance when you only need specific fields:
// Only fetch id and name - faster for large tablesconst orgNames = await db.organization.findMany({ select: { id: true, name: true, },});Transactions
Use transactions for operations that must succeed or fail together:
import { generateId } from '@kit/shared/utils';// Create organization with initial member in a transactionconst result = await db.$transaction(async (tx) => { const org = await tx.organization.create({ data: { id: generateId(), name: 'New Startup', slug: 'new-startup', createdAt: new Date(), }, }); const member = await tx.member.create({ data: { id: generateId(), organizationId: org.id, userId: currentUserId, role: 'owner', createdAt: new Date(), }, }); return { org, member };});Decision Rules
Use findUnique when:
- Querying by primary key or unique constraint
- You expect exactly one result or null
- Performance matters (uses indexed lookup)
Use findMany when:
- Querying multiple records with filters
- You need pagination with
skip/take - Ordering or aggregating results
Use include when:
- You need related data immediately
- The relation is small (few records)
- You'll access the nested data in your code
Use select when:
- You only need specific fields
- Table has many columns or large text fields
- Optimizing response size for client components
If unsure: Start with findUnique/findMany without include. Add relations when you discover you need them.
Query Pitfalls
- Forgetting
organizationIdfilter - Leaks data across tenants; always scope queries to the current organization - Using
findMany()without limits - Can crash memory on large tables; always usetake: Nfor safety - Nested
includecreating N+1 queries - Deeply nested includes can explode into many queries; preferselectfor specific fields - Not handling
nullfromfindUnique- Returnsnullwhen not found; always check before accessing properties - Updating without
whereconstraint - Can update more records than intended; be explicit with yourwhereclause - Missing indexes on filtered columns -
findManyfilters become slow on large tables; add indexes for frequently queried columns
For more Prisma patterns, see the Prisma Client documentation.
Next: Rate Limit Service →