Renovate kept proposing Prisma 7 even though we deliberately downgraded to Prisma 6 in PR #3 — `nestjs-prisma@0.27.0` is incompatible with Prisma 7's driver-adapter contract, and the upgrade is non-trivial enough (PrismaClient instantiation, request- scoped lifecycle, schema validation) to warrant its own ADR rather than a silent Renovate merge. Add an `enabled: false` packageRule for `matchUpdateTypes: ["major"]` on `prisma`, `@prisma/*`, `nestjs-prisma`. Patch and minor bumps of the 6.x line keep flowing. Document the pin and its triggers in ADR-0006: - New "Prisma version pin: 6.x in v1" subsection — explains the current narrowing of "latest stable major" to 6.x and the two triggers for revisiting (nestjs-prisma catches up, or we drop the wrapper). - §Confirmation updated to surface the pin and the Renovate rule. The matching open PR proposing Prisma 7 will be closed manually on Gitea; once this lands, Renovate stops re-creating it.
7.0 KiB
status, date, decision-makers, tags
| status | date | decision-makers | tags | ||
|---|---|---|---|---|---|
| accepted | 2026-04-29 | R&D Lead |
|
Persistence — PostgreSQL with Prisma
Context and Problem Statement
The BFF owns two persistent concerns: session state (covered by Redis in the future ADR-0009) and business data owned directly by the portal — the portal is not a pure proxy. Business data is relational, multi-audience (workforce vs. external customers), audit-sensitive, and must run on-prem.
Which database engine and which data-access layer do we adopt?
Decision Drivers
- On-prem deployment (no managed-service shortcut).
- Multi-audience data with strict isolation between workforce and customer scopes (Row-Level Security desirable).
- ACID transactions and a well-understood operational story (HA, backup, point-in-time recovery).
- TypeScript-first developer experience for the BFF.
- Long-term licensing safety — open source, no recent license-change risk (e.g. SSPL).
Considered Options
Database engine
- PostgreSQL (latest stable major). (Chosen.)
- MariaDB / MySQL.
- SQL Server (would imply Microsoft licensing terms).
- MongoDB / DocumentDB (rejected — not a fit for relational, audit-heavy business data; SSPL concerns).
Data-access layer
- Prisma (latest stable major). (Chosen.)
- Drizzle.
- Kysely.
- TypeORM.
- Plain
pgdriver + hand-written SQL.
Decision Outcome
Chosen options: PostgreSQL as the engine, Prisma as the data-access layer.
The engine is pinned to the latest stable major at workspace bootstrap, and tracks the upstream support window thereafter.
Prisma is wired into NestJS via the nestjs-prisma integration, with a dedicated PrismaService extending PrismaClient and exposed through DI. Migrations are managed by prisma migrate, committed to the repository, validated in CI, and applied through a controlled deployment step (covered by a future infrastructure ADR).
Prisma version pin: 6.x in v1
The "latest stable major" rule above is temporarily narrowed to Prisma 6.x in v1. Reason: at the time of workspace bootstrap, Prisma 7 was the latest major, but nestjs-prisma@0.27.0 (the only maintained NestJS integration) is not compatible with Prisma 7 — PrismaClient instantiation throws under the new driver-adapter contract. We hit this directly in PR #3 and downgraded to Prisma 6.
Concretely:
prismaand@prisma/clientare constrained to6.xinpackage.json.renovate.jsondeclares anenabled: falserule for major bumps ofprisma,@prisma/*,nestjs-prismaso Renovate stops re-proposing the upgrade until we lift the pin.
This pin is revisited (and an upgrade ADR is opened) when either of these triggers fires:
nestjs-prismaships a release that supports Prisma 7 (the wrapper catches up); or- We decide to drop
nestjs-prismain favour of a hand-rolledPrismaModule(the wrapper is replaced).
Either path is a non-trivial change — schema, client instantiation, and the request-scoped lifecycle all need to be re-validated — so it warrants its own ADR rather than a silent Renovate merge.
Consequences
- Good, because PostgreSQL is the de facto enterprise open-source RDBMS — mature HA, replication, RLS, JSONB, full-text search.
- Good, because RLS gives a strong substrate for the workforce/customer isolation called for by ADR-0007 (identity model, future).
- Good, because Prisma's schema-first model is a single source of truth, with type generation and a readable migration log.
- Good, because Prisma's NestJS integration is well-trodden territory.
- Bad, because Prisma adds a generated client and a query engine binary — operational surface to ship and version.
- Bad, because Prisma's escape hatch for advanced PostgreSQL features (advanced CTEs, window functions, RLS policies) is
$queryRaw— usable, but a reminder that not everything goes through the ORM. We will not fight Prisma when raw SQL is the right tool. - Neutral, because Drizzle is a credible "à la pointe" alternative; we accept the more conservative choice for now and re-evaluate at the next major architectural review.
Confirmation
apps/portal-bffdepends onprismaand@prisma/client, both pinned to6.x(see "Prisma version pin: 6.x in v1" above).- A single
schema.prismalives atapps/portal-bff/prisma/schema.prisma. nestjs-prisma'sPrismaModuleis imported globally in the BFF.renovate.jsoncarries anenabled: falserule onmatchUpdateTypes: ["major"]forprisma,@prisma/*,nestjs-prisma. The rule disappears in the same PR as the Prisma 7 upgrade ADR.- CI runs
prisma validateandprisma migrate diffagainst the staging schema. - Database engine version is pinned in deployment manifests.
Pros and Cons of the Options
PostgreSQL (chosen)
- Good, because mature, open source under the PostgreSQL License (permissive), broad on-prem operational tooling.
- Good, because RLS is first-class — directly serves multi-audience isolation.
- Good, because JSONB + relational hybrid suits BFF data shapes.
- Bad, because operational complexity (HA, backups, tuning) must be owned — true of any on-prem RDBMS.
MariaDB / MySQL
- Good, because operationally well-known.
- Bad, because feature gap vs. PostgreSQL (RLS, JSON ergonomics, CTE maturity, generated columns).
MongoDB
- Good, because the document model fits some BFF caches.
- Bad, because business data here is relational; forcing a document store would push joins into the application — bricolage.
- Bad, because the SSPL license raises enterprise legal complications.
Prisma (chosen)
- Good, because schema-first declarative, strong DX, type generation, mainstream in 2026.
- Good, because a mature NestJS integration exists.
- Bad, because the query engine binary adds a deployment artifact.
- Bad, because some PostgreSQL features require
$queryRaw.
Drizzle
- Good, because lighter, closer to SQL, faster, type-safe.
- Bad, because younger (2022) — smaller enterprise track record than Prisma. Kept on the watch list for re-evaluation.
Kysely
- Good, because elegant query-builder, no ORM magic, fully type-safe.
- Bad, because no schema/migrations of its own — must be paired with another tool, increasing surface area.
TypeORM
- Good, because long-standing default in NestJS history.
- Bad, because design issues (decorator hell, runtime metadata fragility, migration story) and slowing maintenance — community momentum has shifted to Prisma and Drizzle.
Plain pg driver + hand-written SQL
- Good, because zero abstraction; full control.
- Bad, because every cross-cutting concern (transactions, connection pooling, type mapping, migrations) is hand-built — the very bricolage we are forbidden.
More Information
- PostgreSQL: https://www.postgresql.org/
- Prisma: https://www.prisma.io/
nestjs-prisma: https://nestjs-prisma.dev/- Related ADRs: ADR-0005, ADR-0007 (identity model + RLS use, future), ADR-0009 (sessions in Redis, future), future infrastructure ADRs (Postgres HA, backup).