Best practices for organizing Prisma schema in medium-sized projects? #29901
Replies: 6 comments 5 replies
|
For 30–50 models, I would use the multi-file schema, but split by domain rather than creating one file per model. A practical layout is: With current Prisma, point import { defineConfig, env } from 'prisma/config'
export default defineConfig({
schema: 'prisma/schema',
migrations: {
path: 'prisma/migrations',
},
datasource: {
url: env('DATABASE_URL'),
},
})Prisma loads the A few rules have kept this manageable in larger projects:
If the project is still small and the domain boundaries are unclear, one file is fine. At 30–50 models, though, a domain-based split usually pays for itself without changing how Prisma Client or migrations work. |
|
paulodearaujo's folder-per-domain layout is the right shape for the schema side. One thing worth adding for the "how do you organize... Prisma Client" part of your question, since that's less settled than the schema split. The instinct to avoid a client-per-domain is correct (you'd end up with N connection pools and no single source of truth for // src/db/extensions/billing.ts
import { Prisma } from '../generated/client';
export const billingExtension = Prisma.defineExtension({
name: 'billing',
model: {
invoice: {
async markPaid(id: string) {
return prisma.invoice.update({ where: { id }, data: { status: 'PAID', paidAt: new Date() } });
},
},
},
});// src/db.ts
import { PrismaClient } from './generated/client';
import { billingExtension } from './extensions/billing';
import { catalogExtension } from './extensions/catalog';
const prisma = new PrismaClient().$extends(billingExtension).$extends(catalogExtension);
export default prisma;This gives you the same domain boundaries you already have in the schema files, but on the client side. One more thing worth locking down early at your scale: seed script organization. With |
|
Good timing on this question — Prisma's multi-file schema support changed things a lot for projects at your scale. On the schema organization question For 30–50 models, the multi-file schema is the right move, but the key is splitting by domain rather than by model. One file per model will leave you with 50 tiny files that are hard to navigate. One big file has obvious problems. Domain-based split hits the sweet spot: Prisma loads all On PrismaClient organization This part trips up a lot of projects at your scale. The core rule: one PrismaClient instance for the whole app. Multiple instances = multiple connection pools, which causes issues under load. Export a singleton: // src/db/client.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;For query organization at 30–50 models, I'd recommend a pragmatic split rather than going full repository pattern everywhere:
For example: // src/billing/billing.queries.ts
export const invoiceFilters = {
forCustomer: (id: string): Prisma.InvoiceWhereInput => ({ customerId: id }),
unpaid: (): Prisma.InvoiceWhereInput => ({ status: { in: ['PENDING', 'OVERDUE'] } }),
};
// src/billing/billing.service.ts
const invoices = await prisma.invoice.findMany({
where: {
...invoiceFilters.forCustomer(customerId),
...invoiceFilters.unpaid(),
},
});This avoids the overhead of full repository classes while still keeping reusable query logic in one place. On migrations Keep one Quick summary for your scale
At 30–50 models you have enough to feel the pain of bad organization but not so much that you need a heavy repository abstraction everywhere — this middle path handles it well. |
|
For a project anticipating 30–50 models, managing a single monolithic 1. Enable the Multi-File Schema Feature (
|
|
One thing I'd clarify in the existing answers is the version-specific part. For a new Prisma 7 project, I wouldn't recommend enabling For example: // prisma.config.ts
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: env("DATABASE_URL"),
},
});Then I would structure the schema roughly by domain: The important distinction is that these files are still one Prisma schema. Splitting them doesn't create separate modules or separate migration histories, so relations can cross domain files normally. For 30–50 models, I'd also avoid creating one PrismaClient per domain. Keep a single client instance and organize the application layer around domains instead: That gives you domain boundaries without creating multiple connection pools. For migrations, I'd keep exactly one migration history even with multiple schema files. The migration history represents the database as a whole, not individual Prisma schema files. So my rule of thumb would be:
The main benefit of multi-file schemas at this size isn't performance or a different Prisma architecture — it's reducing merge conflicts and making ownership/navigation clearer for developers. |
|
For 30–50 models, the multi-file schema approach (stable since Prisma 5.15, enabled with Directory layout
generator client {
provider = "prisma-client-js"
previewFeatures = ["prismaSchemaFolder"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}Then each domain file only contains its own models, enums, and relations. Prisma resolves cross-file relations automatically. Practical tips
Single file is fine up to ~20 models. Beyond that, the multi-file approach pays off in readability and merge-conflict reduction alone. |
Uh oh!
There was an error while loading. Please reload this page.
I'm building a full-stack application using Prisma with PostgreSQL and expect around 30–50 models in the future.
What are the recommended practices for organizing the Prisma schema?
I'd appreciate any advice or examples from production projects.
All reactions