Prisma ORM
Learn how to use Prisma ORM in Sushify Next.js
Prisma is the default ORM in Sushify Next.js, providing a type-safe and intuitive way to interact with your PostgreSQL database.
What is Prisma?
Prisma is a next-generation ORM that makes database access easy with an auto-generated and type-safe query builder. It consists of:
- Prisma Client: Auto-generated database client
- Prisma Migrate: Declarative data modeling and migrations
- Prisma Studio: Visual database browser and editor
Setup
1. Schema Location
The Prisma schema is located at:
packages/database/prisma/schema.prismaThis file defines your database structure, models, and relationships.
The schema starts with the datasource configuration:
datasource db {
provider = "postgresql" // ⚠️ Must match Better Auth adapter provider
url = env("DATABASE_URL")
}Important: The provider value here must match the provider in your Better Auth configuration (packages/auth/auth.ts). See the Database Overview for more details.
2. Generate Prisma Client
After any schema changes, regenerate the Prisma Client:
pnpm --filter database generateThis command:
- Generates the type-safe Prisma Client
- Creates Zod schemas for validation
- Updates TypeScript types
3. Push Schema to Database
For development, push your schema directly to the database:
pnpm --filter database pushThis syncs your Prisma schema with the database without creating migration files.
4. Create Migrations (Production)
For production environments, create and apply migrations:
pnpm --filter database migrateThis creates migration files in packages/database/prisma/migrations/ and applies them to your database.
Using Prisma Client
Importing the Client
Import the database client from the package:
import { db } from "@repo/database";The client is a singleton, ensuring you reuse the same connection across your application.
Basic Queries
Find Many Records
// Get all users
const users = await db.user.findMany();
// Get users with filtering
const verifiedUsers = await db.user.findMany({
where: {
emailVerified: true,
},
});
// Pagination
const users = await db.user.findMany({
take: 10, // Limit
skip: 20, // Offset
});
// Search with contains
const users = await db.user.findMany({
where: {
name: {
contains: "john",
mode: "insensitive", // Case-insensitive search
},
},
});Find Unique Record
// Find by unique field
const user = await db.user.findUnique({
where: {
id: "user-id",
},
});
// Find by email
const user = await db.user.findUnique({
where: {
email: "user@example.com",
},
});
// Throw error if not found
const user = await db.user.findUniqueOrThrow({
where: {
id: "user-id",
},
});Find First Record
const user = await db.user.findFirst({
where: {
emailVerified: true,
},
orderBy: {
createdAt: "desc",
},
});Creating Records
// Create a single record
const user = await db.user.create({
data: {
email: "user@example.com",
name: "John Doe",
emailVerified: true,
createdAt: new Date(),
updatedAt: new Date(),
},
});
// Create with relations
const user = await db.user.create({
data: {
email: "user@example.com",
name: "John Doe",
emailVerified: true,
createdAt: new Date(),
updatedAt: new Date(),
accounts: {
create: {
providerId: "github",
accountId: "github-user-id",
createdAt: new Date(),
updatedAt: new Date(),
},
},
},
include: {
accounts: true,
},
});Updating Records
// Update a record
const user = await db.user.update({
where: {
id: "user-id",
},
data: {
name: "Jane Doe",
updatedAt: new Date(),
},
});
// Update many records
const result = await db.user.updateMany({
where: {
emailVerified: false,
},
data: {
banned: true,
},
});
console.log(`Updated ${result.count} users`);Deleting Records
// Delete a record
const user = await db.user.delete({
where: {
id: "user-id",
},
});
// Delete many records
const result = await db.user.deleteMany({
where: {
banned: true,
},
});
console.log(`Deleted ${result.count} users`);Counting Records
// Count all records
const count = await db.user.count();
// Count with filter
const verifiedCount = await db.user.count({
where: {
emailVerified: true,
},
});Relations
Prisma makes working with related data intuitive.
Include Relations
// Include related accounts
const user = await db.user.findUnique({
where: {
id: "user-id",
},
include: {
accounts: true,
sessions: true,
},
});
// Nested includes
const organization = await db.organization.findUnique({
where: {
id: "org-id",
},
include: {
members: {
include: {
user: true,
},
},
},
});Select Specific Fields
// Select only needed fields
const user = await db.user.findUnique({
where: {
id: "user-id",
},
select: {
id: true,
name: true,
email: true,
accounts: {
select: {
providerId: true,
},
},
},
});Filter by Relations
// Find users who have GitHub accounts
const usersWithGithub = await db.user.findMany({
where: {
accounts: {
some: {
providerId: "github",
},
},
},
});
// Find organizations with admin members
const orgs = await db.organization.findMany({
where: {
members: {
some: {
role: "admin",
},
},
},
});Transactions
Prisma supports transactions to ensure data consistency.
Interactive Transactions
const result = await db.$transaction(async (tx) => {
// Create user
const user = await tx.user.create({
data: {
email: "user@example.com",
name: "Sushify",
emailVerified: true,
createdAt: new Date(),
updatedAt: new Date(),
},
});
// Create organization
const org = await tx.organization.create({
data: {
name: "Sushify Dev",
createdAt: new Date(),
},
});
// Add user as member
await tx.member.create({
data: {
organizationId: org.id,
userId: user.id,
role: "admin",
createdAt: new Date(),
},
});
return { user, org };
});Sequential Transactions
const [user, updatedOrg] = await db.$transaction([
db.user.create({
data: {
email: "user@example.com",
name: "Sushify",
emailVerified: true,
createdAt: new Date(),
updatedAt: new Date(),
},
}),
db.organization.update({
where: { id: "org-id" },
data: { name: "Updated Name" },
}),
]);Pre-built Queries
Sushify Next.js includes pre-built query functions in packages/database/prisma/queries/:
User Queries
import {
getUsers,
getUserById,
getUserByEmail,
createUser,
updateUser,
} from "@repo/database";
// Get paginated users
const users = await getUsers({
limit: 10,
offset: 0,
query: "john", // Optional search term
});
// Get user by ID
const user = await getUserById("user-id");
// Get user by email
const user = await getUserByEmail("user@example.com");
// Create user
const newUser = await createUser({
email: "user@example.com",
name: "Sushify",
role: "user",
emailVerified: true,
onboardingComplete: false,
});
// Update user
const updatedUser = await updateUser({
id: "user-id",
name: "Sushify Dev",
});Prisma Studio
Prisma Studio is a visual database browser that allows you to:
- View and edit data
- Filter and sort records
- Explore relationships
- Run queries
Open Prisma Studio
pnpm --filter database studioThis opens Prisma Studio at http://localhost:5555.
Best Practices
1. Select Only Needed Fields
Reduce data transfer by selecting only required fields:
const users = await db.user.findMany({
select: {
id: true,
name: true,
email: true,
},
});2. Use Pagination
For large datasets, always use pagination:
const users = await db.user.findMany({
take: 50,
skip: page * 50,
});3. Handle Errors
Always handle database errors appropriately:
try {
const user = await db.user.create({ ... });
} catch (error) {
if (error.code === "P2002") {
// Unique constraint violation
throw new Error("User already exists");
}
throw error;
}4. Use Indexes
Define indexes in your schema for frequently queried fields:
model User {
id String @id @default(cuid())
email String @unique
@@index([email])
}