$batch and @optimize
Extras on top of Prisma's API: capabilities Prisma Client does not have, with the same types and code style.
$batch
$batch runs multiple independent read queries in one PostgreSQL round trip.
const dashboard = await prisma.$batch((batch) => ({
totalUsers: batch.user.count(),
activeUsers: batch.user.count({
where: { status: 'ACTIVE' },
}),
recentProjects: batch.project.findMany({
take: 5,
orderBy: { createdAt: 'desc' },
include: { organization: true },
}),
taskStats: batch.task.aggregate({
_count: true,
_avg: { estimatedHours: true },
where: { status: 'IN_PROGRESS' },
}),
}))Rules
Do not await inside the callback.
// Incorrect
await prisma.$batch(async (batch) => ({
users: await batch.user.findMany(),
}))
// Correct
await prisma.$batch((batch) => ({
users: batch.user.findMany(),
}))Best use cases
- dashboards
- analytics summaries
- counts + page data
- multiple independent aggregates
- splitting unrelated heavy reads instead of one massive include tree
Limitations: PostgreSQL only. Queries are independent and not transactional. Use $transaction when you need transactional guarantees.@optimize
For the hottest query shapes you can prebake SQL at generate time. Matching runtime shapes execute the baked SQL directly; non-matching shapes use normal runtime generation.
Basic prebaked query
/// @optimize {
/// "method": "findMany",
/// "query": {
/// "where": { "status": "ACTIVE" }
/// }
/// }
model User {
id Int @id
status String
}Dynamic parameters
Values prefixed with $ become bind parameters at runtime.
/// @optimize {
/// "method": "findMany",
/// "query": {
/// "where": { "status": "$status" },
/// "skip": "$skip",
/// "take": "$take"
/// }
/// }Nested include
/// @optimize {
/// "method": "findMany",
/// "query": {
/// "include": {
/// "posts": {
/// "where": { "published": true },
/// "orderBy": { "createdAt": "desc" },
/// "take": 5
/// }
/// }
/// }
/// }@optimize is optional — it only removes the conversion overhead of repeated hot query shapes. Seegenerator configuration for the skipInvalid option if a directive should be skipped instead of failing generation.
Do you need @optimize? No. Runtime generation already covers every supported shape; prebaking is a micro-optimization for your hottest endpoints.