Overview
Prisma provides industry-leading TypeScript support with fully type-safe database queries, automatic type generation, and compile-time error detection.
Generated Types
Automatic Type Generation
Prisma generates TypeScript types from your schema:
This creates types in node_modules/@prisma/client:
import { PrismaClient, User, Post } from '@prisma/client'
const prisma = new PrismaClient()
// User and Post are fully typed
const user: User = await prisma.user.findUnique({
where: { id: '1' },
})
Model Types
Each model becomes a TypeScript type:
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
posts Post[]
}
Generates:
type User = {
id: string
email: string
name: string | null
createdAt: Date
}
Optional fields in Prisma (name String?) become nullable types in TypeScript (name: string | null).
Query Result Types
Basic Queries
// Type: User
const user = await prisma.user.findUnique({
where: { id: '1' },
})
// Type: User[]
const users = await prisma.user.findMany()
// Type: User | null
const maybeUser = await prisma.user.findFirst({
where: { email: 'user@example.com' },
})
Select Types
When using select, Prisma narrows the return type:
const user = await prisma.user.findUnique({
where: { id: '1' },
select: {
id: true,
email: true,
// name excluded
},
})
// Type: { id: string; email: string } | null
Include Types
Relations are properly typed:
const user = await prisma.user.findUnique({
where: { id: '1' },
include: {
posts: true,
},
})
// Type: User & { posts: Post[] } | null
Nested Selections
const user = await prisma.user.findUnique({
where: { id: '1' },
select: {
email: true,
posts: {
select: {
title: true,
published: true,
},
},
},
})
// Type: {
// email: string
// posts: {
// title: string
// published: boolean
// }[]
// } | null
Type Utilities
Prisma Namespace
Access generated types via the Prisma namespace:
import { Prisma } from '@prisma/client'
// Input types for create operations
type UserCreateInput = Prisma.UserCreateInput
// Where clauses
type UserWhereInput = Prisma.UserWhereInput
// Update input
type UserUpdateInput = Prisma.UserUpdateInput
Validator
Create type-safe reusable queries:
import { Prisma } from '@prisma/client'
const userWithPosts = Prisma.validator<Prisma.UserDefaultArgs>()({
include: { posts: true },
})
type UserWithPosts = Prisma.UserGetPayload<typeof userWithPosts>
// Use in function signatures
function processUser(user: UserWithPosts) {
console.log(user.posts) // Typed as Post[]
}
Get Payload
Extract types from query objects:
import { Prisma } from '@prisma/client'
const userArgs = {
select: {
id: true,
email: true,
posts: {
select: {
title: true,
},
},
},
} satisfies Prisma.UserDefaultArgs
type UserPayload = Prisma.UserGetPayload<typeof userArgs>
// Type: {
// id: string
// email: string
// posts: { title: string }[]
// }
Advanced Patterns
Generic Repository Pattern
import { Prisma, PrismaClient } from '@prisma/client'
type ModelName = Prisma.ModelName
type Models = {
[K in ModelName]: PrismaClient[Uncapitalize<K>]
}
class Repository<T extends ModelName> {
private model: Models[T]
constructor(
private prisma: PrismaClient,
private modelName: T,
) {
this.model = prisma[modelName.toLowerCase()] as Models[T]
}
async findById(id: string) {
return await this.model.findUnique({
where: { id },
})
}
}
const userRepo = new Repository(prisma, 'User')
const user = await userRepo.findById('1') // Typed as User
Conditional Types for Relations
import { Prisma } from '@prisma/client'
type UserArgs<T extends boolean> = T extends true
? Prisma.UserGetPayload<{
include: { posts: true }
}>
: Prisma.UserGetPayload<{}>
async function getUser<T extends boolean>(
id: string,
includePosts: T,
): Promise<UserArgs<T> | null> {
return await prisma.user.findUnique({
where: { id },
include: includePosts ? { posts: true } : undefined,
}) as UserArgs<T> | null
}
const userWithPosts = await getUser('1', true)
// Type: User & { posts: Post[] }
const userOnly = await getUser('1', false)
// Type: User
Type-Safe Middleware
import { Prisma } from '@prisma/client'
prisma.$use(async (params, next) => {
// params.model is typed
if (params.model === 'User') {
// params.args is typed based on action
if (params.action === 'create') {
const args = params.args as Prisma.UserCreateArgs
console.log('Creating user:', args.data.email)
}
}
return next(params)
})
Enum Types
Prisma enums become TypeScript enums:
enum Role {
USER
ADMIN
MODERATOR
}
model User {
id String @id
role Role @default(USER)
}
Usage:
import { Role } from '@prisma/client'
const admin = await prisma.user.create({
data: {
email: 'admin@example.com',
role: Role.ADMIN, // Type-safe enum value
},
})
// Type error if using invalid value
const invalid = await prisma.user.create({
data: {
email: 'user@example.com',
role: 'INVALID', // ❌ Type error
},
})
JSON Field Types
JSON fields are typed as Prisma.JsonValue:
model User {
id String @id
metadata Prisma.JsonValue
}
import { Prisma } from '@prisma/client'
type UserMetadata = {
preferences: {
theme: 'light' | 'dark'
notifications: boolean
}
}
const user = await prisma.user.create({
data: {
email: 'user@example.com',
metadata: {
preferences: {
theme: 'dark',
notifications: true,
},
} satisfies UserMetadata,
},
})
// Type assertion for usage
const metadata = user.metadata as UserMetadata
console.log(metadata.preferences.theme)
Use satisfies to validate JSON structure without losing type information:const data = {
theme: 'dark',
notifications: true,
} satisfies UserMetadata['preferences']
Transaction Types
Batch Transactions
const [user, posts, count] = await prisma.$transaction([
prisma.user.create({ data: { email: 'user@example.com' } }),
prisma.post.findMany(),
prisma.user.count(),
])
// Types: [User, Post[], number]
Interactive Transactions
const result = await prisma.$transaction(async (tx) => {
// tx is typed as Prisma.TransactionClient
const user = await tx.user.create({
data: { email: 'user@example.com' },
})
const post = await tx.post.create({
data: {
title: 'First Post',
userId: user.id,
},
})
return { user, post }
})
// Type: { user: User; post: Post }
Raw Query Types
Provide explicit types for raw queries:
type UserWithPostCount = {
id: string
email: string
postCount: bigint
}
const users = await prisma.$queryRaw<UserWithPostCount[]>`
SELECT
u.id,
u.email,
COUNT(p.id) as "postCount"
FROM "User" u
LEFT JOIN "Post" p ON p."userId" = u.id
GROUP BY u.id
`
// users is typed as UserWithPostCount[]
users.forEach(u => {
console.log(u.postCount) // Typed as bigint
})
Raw queries lose automatic type safety. Always provide explicit type annotations.
Compile-Time Validation
Prisma catches errors at compile time:
// ❌ Type error: 'invalidField' doesn't exist
await prisma.user.findMany({
select: {
invalidField: true,
},
})
// ❌ Type error: 'age' expects number, not string
await prisma.user.create({
data: {
email: 'user@example.com',
age: '25',
},
})
// ❌ Type error: Missing required field 'email'
await prisma.user.create({
data: {
name: 'John Doe',
},
})
Strict Null Checks
Enable strict null checking in tsconfig.json:
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true
}
}
Prisma respects null safety:
const user = await prisma.user.findUnique({
where: { id: '1' },
})
// ❌ Type error: user might be null
console.log(user.email)
// ✅ Null check required
if (user) {
console.log(user.email)
}
// ✅ Optional chaining
console.log(user?.email)
// ✅ Or use findUniqueOrThrow
const user = await prisma.user.findUniqueOrThrow({
where: { id: '1' },
})
console.log(user.email) // No null check needed
Client Extensions Type Safety
Extensions maintain type safety:
const extendedPrisma = prisma.$extends({
model: {
user: {
async findByEmail(email: string) {
return await prisma.user.findUnique({
where: { email },
})
},
},
},
})
// Type: User | null
const user = await extendedPrisma.user.findByEmail('user@example.com')
Best Practices
- Enable strict mode in TypeScript configuration
- Use type inference instead of explicit types when possible
- Leverage Prisma.validator for reusable query patterns
- Provide types for raw queries explicitly
- Use satisfies for JSON field validation
- Handle null values properly with strict null checks
- Use enums instead of string literals for better type safety
- Extract complex types into type aliases for reusability
- Use findUniqueOrThrow when you expect a result
- Validate at compile time to catch errors early
Type Safety Checklist