From b5879eeb91e6b7e765e4fd882320ec664b94853b Mon Sep 17 00:00:00 2001 From: Raschid Jimenez Date: Mon, 27 Jul 2026 12:48:28 -0400 Subject: [PATCH 1/6] docs(guides): add mongo migration guide from v6 to next --- MIGRATION.md | 379 ++++++++++++++++++ apps/docs/content/docs/guides/next/meta.json | 3 +- .../guides/next/upgrade-prisma-orm/meta.json | 6 + .../next/upgrade-prisma-orm/mongodb.mdx | 360 +++++++++++++++++ 4 files changed, 747 insertions(+), 1 deletion(-) create mode 100644 MIGRATION.md create mode 100644 apps/docs/content/docs/guides/next/upgrade-prisma-orm/meta.json create mode 100644 apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000000..eeb9fb6fa5 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,379 @@ +# Migrating a MongoDB project from Prisma v6 to Prisma Next + +This guide is for developers moving a Prisma ORM v6 MongoDB project to +[Prisma Next](https://github.com/prisma/prisma-next). Prisma 7 has **no MongoDB connector**, so +Prisma Next is the successor path. This is a **code and workflow migration against the same +database** — your data never moves. + +> [!TIP] +> **Working with an AI coding agent?** Install the companion `prisma-mongodb-upgrade` skill with `npx skills add prisma/skills` to help your agent with this migration. + +## Prerequisites + +- **Node.js 24+** +- **TypeScript 5.9+** +- **MongoDB 8.0+** +- **The `mongodb` node driver 7.x** is a required peer dependency +- **A replica set** if your app uses transactions. Prisma Next's ORM doesn't wrap `$transaction` yet, so multi-document atomicity goes through the driver's sessions (see step 3d), which MongoDB only allows on a replica set. + +> Prisma Next MongoDB is Early Access (GA planned after Postgres), so details can shift between +> releases — check anything below against the `@prisma-next/*` version you install. + +## Migration Overview + +- [ ] **Set up**: scaffold Next and add the MongoDB driver. +- [ ] **Port the schema**: convert your v6 models to a Prisma Next contract. +- [ ] **Port the code**: update client calls, transactions, and aggregations. +- [ ] **Adopt migrations**: replace the `db push` workflow. +- [ ] **Verify**: check index parity and validators, then soak-test reads. +- [ ] **Cut over**: a code-only switch; your data never moves. + +## 1. Set up Prisma Next + +Scaffold the project, then add the MongoDB driver as a peer dependency: + +```bash +npx prisma-next init --yes --target mongodb --authoring psl +``` + +`init` writes a `prisma-next.config.ts` at the repo root. MongoDB is selected by importing the +`@prisma-next/mongo` façade, not by a `provider` string in the schema. Point the connection +at the **same database** your v6 app uses. + +**Before:** + +```prisma +// schema.prisma +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +// Rest of schema... +``` + +**After:** + +```typescript +// prisma-next.config.ts +import { defineConfig } from '@prisma-next/mongo/config'; + +export default defineConfig({ + contract: './src/prisma/contract.prisma', + db: { + connection: process.env['DATABASE_URL'], + }, +}); +``` + +> [!IMPORTANT] +> **MONGODB-AWS auth only:** the `mongodb@^7` driver no longer accepts AWS credentials embedded in the connection string. See the [driver v7 breaking changes](https://github.com/mongodb/node-mongodb-native/blob/HEAD/etc/notes/CHANGES_7.0.0.md#-aws-authentication). + +### Import the client + +The way to instantiate the Prisma client now is from the emitted contract: + +**Before:** + +```typescript +// src/prisma/db.ts +import { PrismaClient } from '../generated/prisma/client'; + +const prisma = new PrismaClient(); + +export { prisma }; +``` + +**After:** + +```typescript +// src/prisma/db.ts +import mongo from '@prisma-next/mongo/runtime'; +import type { Contract } from './contract'; +import contractJson from './contract.json' with { type: 'json' }; + +const db = mongo({ + contractJson, + url: process.env['DATABASE_URL'], + dbName: 'my-app-db', +}); + +export { db }; +``` + +### Generate the contract + +Run `npx prisma-next contract emit` to generate `contract.json` from your `.prisma` contract file. Re-run this whenever you change the contract. + +## 2. Port the Schema to a Contract + +> [!IMPORTANT] +> Prisma Next addresses collections by **storage name**, not model name: `db.orm.users` (the `@@map` name, or the lowercased model name), never `db.orm.User`. Whatever you `@@map` in this step is the exact name every client call uses in step 3. + +The v6 `schema.prisma` is not consumed as-is; it becomes a **contract**, authored in PSL (shown +here) or TypeScript. The syntax is close to v6, with a few differences. + +In the table below, the left column is how you wrote it in v6 and the right column is the Prisma Next equivalent: + +| Concept | v6 (`schema.prisma`) | Prisma Next (`contract.prisma`) | +|---------|----------------------|---------------------------------| +| Datasource | `datasource db { provider = "mongodb" }` | no `provider` (configured in `prisma-next.config.ts`) | +| Id field | `id String @id @default(auto()) @map("_id") @db.ObjectId` | `id ObjectId @id @map("_id")` | +| Embedded document | `type Address { ... }` | `type Address { ... }` (unchanged) | +| Index | `@@index(...)`, synced by `db push` | `@@index(...)` / `@@unique(...)`, applied by migrations | +| Polymorphism | not supported | `@@discriminator(field)` on the base + `@@base(Base, "value")` on each variant.
See the [Base Model and Variants](https://www.prisma.io/docs/orm/next/contract-authoring/psl-syntax#base-models-and-variants) | + +A fuller contract putting these together — enum, embedded type, relation, and polymorphism: + +```prisma +// src/prisma/contract.prisma +enum UserRole { + @@type("mongo/string@1") + Admin = "admin" + Author = "author" + Reader = "reader" +} + +type Address { + street String + city String + country String +} + +model User { + id ObjectId @id @map("_id") + email String + role UserRole + address Address? + posts Post[] + @@map("users") +} + +model Post { + id ObjectId @id @map("_id") + title String + kind String + authorId ObjectId + createdAt DateTime + author User @relation(fields: [authorId], references: [id]) + @@discriminator(kind) + @@index([createdAt(sort: Desc), authorId]) + @@map("posts") +} + +model Article { + summary String + @@base(Post, "article") +} +``` + +## 3. Port client calls + +Names look similar but parity does not hold. This section breaks down each category of operation. + +### 3a. Basic CRUD + +Prisma Next uses a chained API instead of v6's single options object: you start from a collection +(like `db.orm.users`), add filters and options step by step, then finish with a method that runs +the query. Each example shows the v6 call first, then its Prisma Next equivalent (see **Common +mistakes** below for the two rules that trip people up most): + +```typescript +import { db } from './db'; + +// Find one +// v6: await prisma.user.findFirst({ where: { email } }) +await db.orm.users.where({ email: 'alice@example.com' }).first(); + +// Create +// v6: await prisma.user.create({ data: { email, role: 'author' } }) +const user = await db.orm.users.create({ email: 'a@e.com', role: 'author', address: null }); + +// Update one +// v6: await prisma.user.update({ where: { id }, data: { role: 'author' } }) +await db.orm.users.where({ _id: user._id }).update({ role: 'author' }); + +// Update many +// v6: await prisma.user.updateMany({ where: { role: 'reader' }, data: { role: 'author' } }) +await db.orm.users.where({ role: 'reader' }).updateAll({ role: 'author' }); + +// Delete one +// v6: await prisma.user.delete({ where: { id } }) +await db.orm.users.where({ _id: user._id }).delete(); +``` + +**Common mistakes:** + +| ❌ Don't | ✅ Do | Why | +|---------|------|-----| +| `db.orm.User` | `db.orm.users` | address by storage name, not model name | +| `.where({ email: { equals: 'x' } })` | `.where({ email: 'x' })` | `.where(...)` takes a plain equality object | +| `.update({ ... })` with no `.where(...)` | `.where(...).update({ ... })` | every mutation except `create` needs a `.where(...)` first | + +### 3b. Relations and polymorphism + +```typescript +// Eager-load a relation +// v6: await prisma.post.findMany({ include: { author: true } }) +const withAuthor = await db.orm.posts.include('author').all(); + +// Query one variant of a polymorphic collection +const articles = await db.orm.posts.variant('Article').all(); +``` + +### 3c. Aggregations + +v6's `groupBy` / `aggregate` become a chain of steps. `acc` provides the group totals +(`acc.count()`, `acc.max(...)`, etc.) — the equivalent of v6's `_count` / `_sum` / `_max`. + +Before: + +```typescript +await prisma.post.groupBy({ + by: ['kind'], + where: { authorId }, + _count: { _all: true }, + _max: { createdAt: true }, + orderBy: { _count: { kind: 'desc' } }, +}); +``` + +After: + +```typescript +import { acc } from '@prisma-next/mongo-query-builder'; + +const runtime = await db.runtime(); +const plan = db.query + .from('posts') + .match((f) => f.authorId.eq(authorId)) + .group((f) => ({ _id: f.kind, postCount: acc.count(), latest: acc.max(f.createdAt) })) + .sort({ postCount: -1 }) + .build(); +const byKind = await runtime.execute(plan); +``` + +### 3d. Transactions + +Prisma Next has no `transaction` method for MongoDB yet. For multi-document transactions, use the `mongodb` driver's [Transaction API](https://www.mongodb.com/docs/drivers/node/current/crud/transactions/transaction-conv/). + +**Before:** + +```typescript +await prisma.$transaction(async (tx) => { + // ...writes... +}); +``` + +**After:** + +```typescript +import { MongoClient } from 'mongodb'; + +const client = new MongoClient(DATABASE_URL); +const session = client.startSession(); +const mdb = client.db('my-app-db'); + +try { + await session.withTransaction(async () => { + // Pass { session } to EVERY operation — an op without it silently runs + // outside the transaction. + await mdb + .collection('users') + .insertOne({ email: 'a@e.com', role: 'author' }, { session }); + await mdb + .collection('posts') + .updateOne({ _id: postId }, { $set: { authorId } }, { session }); + }); +} finally { + await session.endSession(); +} +``` + +### 3e. Raw operations + +Use the pipeline builder for aggregations, or drop to the `mongodb` driver for arbitrary commands. + +### Quick reference table + +| v6 | Prisma Next | +|----|-------------| +| `prisma.user.findMany(...)` | `db.orm.users.where({ ... }).all()` | +| `prisma.user.findFirst(...)` | `db.orm.users.where({ ... }).first()` | +| `create` / `update` / `delete` / `upsert` | same names on `db.orm.` | +| `updateMany` / `deleteMany` | `updateAll` / `deleteAll` | +| `aggregate` / `groupBy` | `db.query.from(...).group(...).build()` → `runtime.execute(plan)` | +| `$runCommandRaw` / `findRaw` / `aggregateRaw` | pipeline builder, or the `mongodb` driver directly (arbitrary commands) | +| `$transaction(...)` | No façade wrapper yet — use driver sessions on a replica set | +| `$connect` / `$disconnect` | lazy connect on first use; `db.close()` to disconnect | + +## 4. Adopt the migration lifecycle + +v6 MongoDB had no Prisma Migrate (`db push` only). Prisma Next gives MongoDB first-class, +contract-driven migrations. + +Unlike v6's `db push` (ephemeral, no history), Next migrations are: +- **Reviewable**: diffs are packages you commit +- **Reproducible**: same package → same DB state across envs +- **Auditable**: migration history = schema evolution timeline + +For local dev, `db update` mimics `db push`. For shared/prod, use the migration workflow. + +The CLI reads the target from `prisma-next.config.ts`: + +```bash +# Local dev: diff the contract against the live DB and apply directly (no history kept), +# the closest analogue to the old `db push`. Mongo dev scaffolds often need ?replicaSet=rs0. +npx prisma-next contract emit +npx prisma-next db update --db "$DATABASE_URL" + +# Shared branches / production: write a reviewable migration package, then apply it. +npx prisma-next migration plan --name add_posts_indexes +npx prisma-next migrate --db "$DATABASE_URL" +npx prisma-next db verify --db "$DATABASE_URL" # check the DB matches the contract +``` + +**Good to know:** + +- You never hand-write migration steps. You declare indexes and validators in the contract + (step 2), and `migration plan` works out the changes for you. +- On a database Prisma Next hasn't managed before, run `db init` once first. If you ever fix + something by hand, run `db sign` so Prisma Next knows the current state. +- If a `migrate` run gets interrupted, just run it again. It picks up where it left off. Run + `db verify` any time to check the database still matches your contract. +- By default, Prisma Next adds strict server-side validators to your collections using MongoDB's `$jsonSchema`. Before running this in + production, make sure your existing documents pass them. + +## 5. Pre-flight checklist (before production cutover) + +Work through this list before you switch production traffic over: + +- [ ] Your Prisma Next config points at the **same** database and connection string as v6. +- [ ] Your MongoDB server is on 8.0 or newer. +- [ ] **Dry run first.** Rehearse the whole flow on a throwaway copy of your database (`contract + emit`, `migration plan`, `migrate`, `db verify`) and make sure `db verify` passes before you + touch production. +- [ ] **Index parity.** The indexes on each collection (`db.collection.getIndexes()`) match your + contract. +- [ ] **Validators.** Your existing documents pass the strict validators Prisma Next adds to each + collection (see *Good to know* in step 4). +- [ ] **Addressing.** Every call site uses storage names like `db.orm.users`, not model names. +- [ ] **Transactions.** Every v6 `$transaction` now has a driver-session equivalent. +- [ ] **Raw calls.** Every `$runCommandRaw`, `findRaw`, and `aggregateRaw` has a pipeline-builder + or `mongodb`-driver replacement. +- [ ] **Read-only trial run.** Run Prisma Next read-only alongside your v6 app for a while and + compare the results. This surfaces any differences while v6 is still handling all the writes. +- [ ] **Cutover and rollback.** Switch writes over only once the trial run looks good, and keep the + v6 branch deployable. Rolling back is just a code rollback since your data never moved. + +Don't delete the v6 client, schema, or dependencies until this checklist passes. + +## 6. Post-migration: adopting Next workflows + +Once you've cut over, this is the day-to-day workflow: + +- **Schema changes.** Edit the contract, then run `contract emit`, `migration plan`, and `migrate` (the same loop from step 4). +- **Troubleshooting.** `db verify` checks the database against your contract, and `db sign` records any fixes you make by hand. +- **Performance.** Your aggregations now compile to native MongoDB pipelines, so it's worth benchmarking them against your old v6 raw calls. + +See [Prisma Next docs](https://github.com/prisma/prisma-next) for pipeline builder patterns, relation loading strategies, and advanced contract features. This guide gets you across the bridge; Prisma Next's docs are the source of truth from here on. diff --git a/apps/docs/content/docs/guides/next/meta.json b/apps/docs/content/docs/guides/next/meta.json index 6f57759d5a..535252ee7a 100644 --- a/apps/docs/content/docs/guides/next/meta.json +++ b/apps/docs/content/docs/guides/next/meta.json @@ -3,6 +3,7 @@ "pages": [ "index", "frameworks", - "runtimes" + "runtimes", + "upgrade-prisma-orm" ] } diff --git a/apps/docs/content/docs/guides/next/upgrade-prisma-orm/meta.json b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/meta.json new file mode 100644 index 0000000000..94beb121a0 --- /dev/null +++ b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/meta.json @@ -0,0 +1,6 @@ +{ + "title": "Upgrade Prisma ORM", + "pages": [ + "mongodb" + ] +} diff --git a/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx new file mode 100644 index 0000000000..6c22722e81 --- /dev/null +++ b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx @@ -0,0 +1,360 @@ +--- +title: MongoDB +description: Migrate a MongoDB project from Prisma v6 to Prisma Next +url: /guides/next/upgrade-prisma-orm/mongodb +metaTitle: How to migrate a MongoDB project from Prisma v6 to Prisma Next +metaDescription: Step-by-step guide to migrating a Prisma ORM v6 MongoDB project to Prisma Next, covering setup, contracts, client calls, migrations, and production cutover. +--- + +This guide is for developers moving a Prisma ORM v6 MongoDB project to **Prisma Next**. Prisma 7 has **no MongoDB connector**, so Prisma Next is the successor path. This is a **code and workflow migration against the same database** — your data never moves. + +:::tip[Working with an AI coding agent?] + +Install the companion [prisma-mongodb-upgrade](https://github.com/prisma/skills/tree/main/prisma-mongodb-upgrade) skill with `npx skills add prisma/skills` to help your agent with this migration. + +::: + +## Prerequisites + +- **Node.js 24+** +- **TypeScript 5.9+** +- **MongoDB 8.0+** +- **The mongodb node driver 7.x** as a peer dependency +- **A replica set** if your app uses [transactions](/orm/prisma-client/queries/transactions). Prisma Next's ORM doesn't wrap `$transaction` yet, so multi-document atomicity goes through the driver's sessions (see step 3d), which MongoDB only allows on a replica set. + +:::info + +Prisma Next MongoDB is Early Access (GA planned after Postgres), so details can shift between releases — check anything below against the `@prisma-next/*` version you install. + +::: + +## Migration Overview + +1. **Set up**: scaffold Next and add the MongoDB driver. +1. **Port the schema**: convert your v6 models to a Prisma Next contract. +1. **Port the code**: update client calls, transactions, and aggregations. +1. **Adopt migrations**: replace the `db push` workflow. +1. **Verify**: check index parity and validators, then soak-test reads. +1. **Cut over**: a code-only switch. + +## 1. Set up Prisma Next + +Scaffold the project, then add the MongoDB driver as a peer dependency: + +```bash +npx prisma-next init --yes --target mongodb --authoring psl +``` + +`init` writes a `prisma-next.config.ts` at the repo root. MongoDB is selected by importing the `@prisma-next/mongo` façade, not by a `provider` string in the schema. Point the connection at the **same database** your v6 app uses. + +```prisma tab="Before" +// schema.prisma +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} + +// Rest of schema... +``` + +```typescript tab="After" +// prisma-next.config.ts +import { defineConfig } from '@prisma-next/mongo/config'; + +export default defineConfig({ + contract: './src/prisma/contract.prisma', + db: { + connection: process.env['DATABASE_URL'], + }, +}); +``` + +:::warning[MONGODB-AWS auth only] + +The `mongodb@^7` driver no longer accepts AWS credentials embedded in the connection string. See the [driver v7 breaking changes](https://github.com/mongodb/node-mongodb-native/blob/HEAD/etc/notes/CHANGES_7.0.0.md#-aws-authentication). + +::: + +### Import the client + +The way to instantiate the Prisma client now is from the emitted contract: + +```typescript tab="Before" +// src/prisma/db.ts +import { PrismaClient } from '../generated/prisma/client'; + +const prisma = new PrismaClient(); + +export { prisma }; +``` + +```typescript tab="After" +// src/prisma/db.ts +import mongo from '@prisma-next/mongo/runtime'; +import type { Contract } from './contract'; +import contractJson from './contract.json' with { type: 'json' }; + +const db = mongo({ + contractJson, + url: process.env['DATABASE_URL'], + dbName: 'my-app-db', +}); + +export { db }; +``` + +### Generate the contract + +Run `npx prisma-next contract emit` to generate `contract.json` from your `.prisma` contract file. Re-run this whenever you change the contract. + +## 2. Port the Schema to a Contract + +:::note + +Prisma Next addresses collections by **storage name**, not model name: `db.orm.users` (the `@@map` name, or the lowercased model name), never `db.orm.User`. Whatever you `@@map` in this step is the exact name every client call uses in step 3. + +::: + +The v6 `schema.prisma` is not consumed as-is; it becomes a **contract**, authored in PSL (shown here) or TypeScript. The syntax is close to v6, with a few differences. + +In the table below, the left column is how you wrote it in v6 and the right column is the Prisma Next equivalent: + +| Concept | v6 (`schema.prisma`) | Prisma Next (`contract.prisma`) | +|---------|----------------------|---------------------------------| +| Datasource | `datasource db { provider = "mongodb" }` | no `provider` (configured in `prisma-next.config.ts`) | +| Id field | `id String @id @default(auto()) @map("_id") @db.ObjectId` | `id ObjectId @id @map("_id")` | +| Embedded document | `type Address { ... }` | `type Address { ... }` (unchanged) | +| Index | `@@index(...)`, synced by `db push` | `@@index(...)` / `@@unique(...)`, applied by migrations | +| Polymorphism | not supported | `@@discriminator(field)` on the base + `@@base(Base, "value")` on each variant.
See the [Base Model and Variants](https://www.prisma.io/docs/orm/next/contract-authoring/psl-syntax#base-models-and-variants) | + +A fuller contract putting these together — enum, embedded type, relation, and polymorphism: + +```prisma +// src/prisma/contract.prisma +enum UserRole { + @@type("mongo/string@1") + Admin = "admin" + Author = "author" + Reader = "reader" +} + +type Address { + street String + city String + country String +} + +model User { + id ObjectId @id @map("_id") + email String + role UserRole + address Address? + posts Post[] + @@map("users") +} + +model Post { + id ObjectId @id @map("_id") + title String + kind String + authorId ObjectId + createdAt DateTime + author User @relation(fields: [authorId], references: [id]) + @@discriminator(kind) + @@index([createdAt(sort: Desc), authorId]) + @@map("posts") +} + +model Article { + summary String + @@base(Post, "article") +} +``` + +## 3. Port client calls + +Names look similar but parity does not hold. This section breaks down each category of operation. + +### 3a. Basic CRUD + +Prisma Next uses a chained API instead of v6's single options object: you start from a collection (like `db.orm.users`), add filters and options step by step, then finish with a method that runs the query. Each example shows the v6 call first, then its Prisma Next equivalent (see **Common mistakes** below for the two rules that trip people up most): + +```typescript +import { db } from './db'; + +// Find one +// v6: await prisma.user.findFirst({ where: { email } }) +await db.orm.users.where({ email: 'alice@example.com' }).first(); + +// Create +// v6: await prisma.user.create({ data: { email, role: 'author' } }) +const user = await db.orm.users.create({ email: 'a@e.com', role: 'author', address: null }); + +// Update one +// v6: await prisma.user.update({ where: { id }, data: { role: 'author' } }) +await db.orm.users.where({ _id: user._id }).update({ role: 'author' }); + +// Update many +// v6: await prisma.user.updateMany({ where: { role: 'reader' }, data: { role: 'author' } }) +await db.orm.users.where({ role: 'reader' }).updateAll({ role: 'author' }); + +// Delete one +// v6: await prisma.user.delete({ where: { id } }) +await db.orm.users.where({ _id: user._id }).delete(); +``` + +**Common mistakes:** + +| ❌ Don't | ✅ Do | Why | +|---------|------|-----| +| `db.orm.User` | `db.orm.users` | address by storage name, not model name | +| `.where({ email: { equals: 'x' } })` | `.where({ email: 'x' })` | `.where(...)` takes a plain equality object | +| `.update({ ... })` with no `.where(...)` | `.where(...).update({ ... })` | every mutation except `create` needs a `.where(...)` first | + +### 3b. Relations and polymorphism + +```typescript +// Eager-load a relation +// v6: await prisma.post.findMany({ include: { author: true } }) +const withAuthor = await db.orm.posts.include('author').all(); + +// Query one variant of a polymorphic collection +const articles = await db.orm.posts.variant('Article').all(); +``` + +### 3c. Aggregations + +v6's `groupBy` / `aggregate` become a chain of steps. `acc` provides the group totals (`acc.count()`, `acc.max(...)`, etc.) — the equivalent of v6's `_count` / `_sum` / `_max`. + +```typescript tab="Before" +await prisma.post.groupBy({ + by: ['kind'], + where: { authorId }, + _count: { _all: true }, + _max: { createdAt: true }, + orderBy: { _count: { kind: 'desc' } }, +}); +``` + +```typescript tab="After" +import { acc } from '@prisma-next/mongo-query-builder'; + +const runtime = await db.runtime(); +const plan = db.query + .from('posts') + .match((f) => f.authorId.eq(authorId)) + .group((f) => ({ _id: f.kind, postCount: acc.count(), latest: acc.max(f.createdAt) })) + .sort({ postCount: -1 }) + .build(); +const byKind = await runtime.execute(plan); +``` + +### 3d. Transactions + +Prisma Next has no `transaction` method for MongoDB yet. For multi-document transactions, use the `mongodb` driver's [Transaction API](https://www.mongodb.com/docs/drivers/node/current/crud/transactions/transaction-conv/). + +```typescript tab="Before" +await prisma.$transaction(async (tx) => { + // ...writes... +}); +``` + +```typescript tab="After" +import { MongoClient } from 'mongodb'; + +const client = new MongoClient(DATABASE_URL); +const session = client.startSession(); +const mdb = client.db('my-app-db'); + +try { + await session.withTransaction(async () => { + // Pass { session } to EVERY operation — an op without it silently runs + // outside the transaction. + await mdb + .collection('users') + .insertOne({ email: 'a@e.com', role: 'author' }, { session }); + await mdb + .collection('posts') + .updateOne({ _id: postId }, { $set: { authorId } }, { session }); + }); +} finally { + await session.endSession(); +} +``` + +### 3e. Raw operations + +Use the pipeline builder for aggregations, or drop to the `mongodb` driver for arbitrary commands. + +### Quick reference table + +| v6 | Prisma Next | +|----|-------------| +| `prisma.user.findMany(...)` | `db.orm.users.where({ ... }).all()` | +| `prisma.user.findFirst(...)` | `db.orm.users.where({ ... }).first()` | +| `create` / `update` / `delete` / `upsert` | same names on `db.orm.` | +| `updateMany` / `deleteMany` | `updateAll` / `deleteAll` | +| `aggregate` / `groupBy` | `db.query.from(...).group(...).build()` → `runtime.execute(plan)` | +| `$runCommandRaw` / `findRaw` / `aggregateRaw` | pipeline builder, or the `mongodb` driver directly (arbitrary commands) | +| `$transaction(...)` | No façade wrapper yet — use driver sessions on a replica set | +| `$connect` / `$disconnect` | lazy connect on first use; `db.close()` to disconnect | + +## 4. Adopt the migration lifecycle + +v6 MongoDB had no Prisma Migrate (`db push` only). Prisma Next gives MongoDB first-class, contract-driven migrations. + +Unlike v6's `db push` (ephemeral, no history), Next migrations are: + +- **Reviewable**: diffs are packages you commit +- **Reproducible**: same package → same DB state across envs +- **Auditable**: migration history = schema evolution timeline + +For local dev, `db update` mimics `db push`. For shared/prod, use the migration workflow. + +The CLI reads the target from `prisma-next.config.ts`: + +```bash +# Local dev: diff the contract against the live DB and apply directly (no history kept), +# the closest analogue to the old `db push`. Mongo dev scaffolds often need ?replicaSet=rs0. +npx prisma-next contract emit +npx prisma-next db update --db "$DATABASE_URL" + +# Shared branches / production: write a reviewable migration package, then apply it. +npx prisma-next migration plan --name add_posts_indexes +npx prisma-next migrate --db "$DATABASE_URL" +npx prisma-next db verify --db "$DATABASE_URL" # check the DB matches the contract +``` + +**Good to know:** + +- You never hand-write migration steps. You declare indexes and validators in the contract (step 2), and `migration plan` works out the changes for you. +- On a database Prisma Next hasn't managed before, run `db init` once first. If you ever fix something by hand, run `db sign` so Prisma Next knows the current state. +- If a `migrate` run gets interrupted, just run it again. It picks up where it left off. Run `db verify` any time to check the database still matches your contract. +- By default, Prisma Next adds strict server-side validators to your collections using MongoDB's `$jsonSchema`. Before running this in production, make sure your existing documents pass them. + +## 5. Pre-flight checklist (before production cutover) + +Work through this list before you switch production traffic over: + +- [ ] Your Prisma Next config points at the **same** database and connection string as v6. +- [ ] Your MongoDB server is on 8.0 or newer. +- [ ] **Dry run first.** Rehearse the whole flow on a throwaway copy of your database (`contract emit`, `migration plan`, `migrate`, `db verify`) and make sure `db verify` passes before you touch production. +- [ ] **Index parity.** The indexes on each collection (`db.collection.getIndexes()`) match your contract. +- [ ] **Validators.** Your existing documents pass the strict validators Prisma Next adds to each collection (see *Good to know* in step 4). +- [ ] **Addressing.** Every call site uses storage names like `db.orm.users`, not model names. +- [ ] **Transactions.** Every v6 `$transaction` now has a driver-session equivalent. +- [ ] **Raw calls.** Every `$runCommandRaw`, `findRaw`, and `aggregateRaw` has a pipeline-builder or `mongodb`-driver replacement. +- [ ] **Read-only trial run.** Run Prisma Next read-only alongside your v6 app for a while and compare the results. This surfaces any differences while v6 is still handling all the writes. +- [ ] **Cutover and rollback.** Switch writes over only once the trial run looks good, and keep the v6 branch deployable. Rolling back is just a code rollback since your data never moved. + +Don't delete the v6 client, schema, or dependencies until this checklist passes. + +## 6. Post-migration: adopting Next workflows + +Once you've cut over, this is the day-to-day workflow: + +- **Schema changes.** Edit the contract, then run `contract emit`, `migration plan`, and `migrate` (the same loop from step 4). +- **Troubleshooting.** `db verify` checks the database against your contract, and `db sign` records any fixes you make by hand. +- **Performance.** Your aggregations now compile to native MongoDB pipelines, so it's worth benchmarking them against your old v6 raw calls. + +See [Prisma Next docs](https://github.com/prisma/prisma-next) for pipeline builder patterns, relation loading strategies, and advanced contract features. This guide gets you across the bridge; Prisma Next's docs are the source of truth from here on. From 46629d1e8f4ac785e65e7ad8c6333c61c1d83eca Mon Sep 17 00:00:00 2001 From: Raschid Jimenez Date: Mon, 27 Jul 2026 13:24:22 -0400 Subject: [PATCH 2/6] improve information order --- .../next/upgrade-prisma-orm/mongodb.mdx | 101 +++++++++--------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx index 6c22722e81..9b22159363 100644 --- a/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx +++ b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx @@ -28,18 +28,15 @@ Prisma Next MongoDB is Early Access (GA planned after Postgres), so details can ::: -## Migration Overview +:::warning -1. **Set up**: scaffold Next and add the MongoDB driver. -1. **Port the schema**: convert your v6 models to a Prisma Next contract. -1. **Port the code**: update client calls, transactions, and aggregations. -1. **Adopt migrations**: replace the `db push` workflow. -1. **Verify**: check index parity and validators, then soak-test reads. -1. **Cut over**: a code-only switch. +The `mongodb@^7` driver no longer accepts AWS credentials embedded in the connection string. See the [driver v7 breaking changes](https://github.com/mongodb/node-mongodb-native/blob/HEAD/etc/notes/CHANGES_7.0.0.md#-aws-authentication). + +::: ## 1. Set up Prisma Next -Scaffold the project, then add the MongoDB driver as a peer dependency: +Scaffold the project: ```bash npx prisma-next init --yes --target mongodb --authoring psl @@ -47,16 +44,6 @@ npx prisma-next init --yes --target mongodb --authoring psl `init` writes a `prisma-next.config.ts` at the repo root. MongoDB is selected by importing the `@prisma-next/mongo` façade, not by a `provider` string in the schema. Point the connection at the **same database** your v6 app uses. -```prisma tab="Before" -// schema.prisma -datasource db { - provider = "mongodb" - url = env("DATABASE_URL") -} - -// Rest of schema... -``` - ```typescript tab="After" // prisma-next.config.ts import { defineConfig } from '@prisma-next/mongo/config'; @@ -69,25 +56,24 @@ export default defineConfig({ }); ``` -:::warning[MONGODB-AWS auth only] +```prisma tab="Before" +// schema.prisma +datasource db { + provider = "mongodb" + url = env("DATABASE_URL") +} -The `mongodb@^7` driver no longer accepts AWS credentials embedded in the connection string. See the [driver v7 breaking changes](https://github.com/mongodb/node-mongodb-native/blob/HEAD/etc/notes/CHANGES_7.0.0.md#-aws-authentication). +// Rest of schema... +``` -::: +### Generate the contract + +Run `npx prisma-next contract emit` to generate `contract.json` from your `.prisma` contract file. Re-run this whenever you change the contract. ### Import the client The way to instantiate the Prisma client now is from the emitted contract: -```typescript tab="Before" -// src/prisma/db.ts -import { PrismaClient } from '../generated/prisma/client'; - -const prisma = new PrismaClient(); - -export { prisma }; -``` - ```typescript tab="After" // src/prisma/db.ts import mongo from '@prisma-next/mongo/runtime'; @@ -103,9 +89,14 @@ const db = mongo({ export { db }; ``` -### Generate the contract +```typescript tab="Before" +// src/prisma/db.ts +import { PrismaClient } from '../generated/prisma/client'; -Run `npx prisma-next contract emit` to generate `contract.json` from your `.prisma` contract file. Re-run this whenever you change the contract. +const prisma = new PrismaClient(); + +export { prisma }; +``` ## 2. Port the Schema to a Contract @@ -119,6 +110,12 @@ The v6 `schema.prisma` is not consumed as-is; it becomes a **contract**, authore In the table below, the left column is how you wrote it in v6 and the right column is the Prisma Next equivalent: +:::tip + +You can use the [prisma-mongodb-upgrade](https://github.com/prisma/skills/tree/main/prisma-mongodb-upgrade) skill to help your agent map the contract to your schema. + +::: + | Concept | v6 (`schema.prisma`) | Prisma Next (`contract.prisma`) | |---------|----------------------|---------------------------------| | Datasource | `datasource db { provider = "mongodb" }` | no `provider` (configured in `prisma-next.config.ts`) | @@ -127,7 +124,7 @@ In the table below, the left column is how you wrote it in v6 and the right colu | Index | `@@index(...)`, synced by `db push` | `@@index(...)` / `@@unique(...)`, applied by migrations | | Polymorphism | not supported | `@@discriminator(field)` on the base + `@@base(Base, "value")` on each variant.
See the [Base Model and Variants](https://www.prisma.io/docs/orm/next/contract-authoring/psl-syntax#base-models-and-variants) | -A fuller contract putting these together — enum, embedded type, relation, and polymorphism: +A fuller contract putting together enum, embedded type, relation, and polymorphism: ```prisma // src/prisma/contract.prisma @@ -177,7 +174,7 @@ Names look similar but parity does not hold. This section breaks down each categ ### 3a. Basic CRUD -Prisma Next uses a chained API instead of v6's single options object: you start from a collection (like `db.orm.users`), add filters and options step by step, then finish with a method that runs the query. Each example shows the v6 call first, then its Prisma Next equivalent (see **Common mistakes** below for the two rules that trip people up most): +Prisma Next uses a chained API instead of v6's single options object: you start from a collection (like `db.orm.users`), add filters and options step by step, then finish with a method that runs the query: ```typescript import { db } from './db'; @@ -205,8 +202,8 @@ await db.orm.users.where({ _id: user._id }).delete(); **Common mistakes:** -| ❌ Don't | ✅ Do | Why | -|---------|------|-----| +| Don't | Do | Why | +|-------|----|-----| | `db.orm.User` | `db.orm.users` | address by storage name, not model name | | `.where({ email: { equals: 'x' } })` | `.where({ email: 'x' })` | `.where(...)` takes a plain equality object | | `.update({ ... })` with no `.where(...)` | `.where(...).update({ ... })` | every mutation except `create` needs a `.where(...)` first | @@ -226,16 +223,6 @@ const articles = await db.orm.posts.variant('Article').all(); v6's `groupBy` / `aggregate` become a chain of steps. `acc` provides the group totals (`acc.count()`, `acc.max(...)`, etc.) — the equivalent of v6's `_count` / `_sum` / `_max`. -```typescript tab="Before" -await prisma.post.groupBy({ - by: ['kind'], - where: { authorId }, - _count: { _all: true }, - _max: { createdAt: true }, - orderBy: { _count: { kind: 'desc' } }, -}); -``` - ```typescript tab="After" import { acc } from '@prisma-next/mongo-query-builder'; @@ -249,16 +236,20 @@ const plan = db.query const byKind = await runtime.execute(plan); ``` -### 3d. Transactions - -Prisma Next has no `transaction` method for MongoDB yet. For multi-document transactions, use the `mongodb` driver's [Transaction API](https://www.mongodb.com/docs/drivers/node/current/crud/transactions/transaction-conv/). - ```typescript tab="Before" -await prisma.$transaction(async (tx) => { - // ...writes... +await prisma.post.groupBy({ + by: ['kind'], + where: { authorId }, + _count: { _all: true }, + _max: { createdAt: true }, + orderBy: { _count: { kind: 'desc' } }, }); ``` +### 3d. Transactions + +Prisma Next has no `transaction` method for MongoDB yet. For multi-document transactions, use the `mongodb` driver's [Transaction API](https://www.mongodb.com/docs/drivers/node/current/crud/transactions/transaction-conv/). + ```typescript tab="After" import { MongoClient } from 'mongodb'; @@ -282,6 +273,12 @@ try { } ``` +```typescript tab="Before" +await prisma.$transaction(async (tx) => { + // ...writes... +}); +``` + ### 3e. Raw operations Use the pipeline builder for aggregations, or drop to the `mongodb` driver for arbitrary commands. From 2e996b7113f71c3eeae56c88dbc7857c3321dc6a Mon Sep 17 00:00:00 2001 From: Raschid Jimenez Date: Mon, 27 Jul 2026 16:23:54 -0400 Subject: [PATCH 3/6] wrap examples in tabs --- .../next/upgrade-prisma-orm/mongodb.mdx | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx index 9b22159363..fdbb185bfd 100644 --- a/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx +++ b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx @@ -176,30 +176,44 @@ Names look similar but parity does not hold. This section breaks down each categ Prisma Next uses a chained API instead of v6's single options object: you start from a collection (like `db.orm.users`), add filters and options step by step, then finish with a method that runs the query: -```typescript +```typescript tab="After" import { db } from './db'; // Find one -// v6: await prisma.user.findFirst({ where: { email } }) await db.orm.users.where({ email: 'alice@example.com' }).first(); // Create -// v6: await prisma.user.create({ data: { email, role: 'author' } }) const user = await db.orm.users.create({ email: 'a@e.com', role: 'author', address: null }); // Update one -// v6: await prisma.user.update({ where: { id }, data: { role: 'author' } }) await db.orm.users.where({ _id: user._id }).update({ role: 'author' }); // Update many -// v6: await prisma.user.updateMany({ where: { role: 'reader' }, data: { role: 'author' } }) await db.orm.users.where({ role: 'reader' }).updateAll({ role: 'author' }); // Delete one -// v6: await prisma.user.delete({ where: { id } }) await db.orm.users.where({ _id: user._id }).delete(); ``` +```typescript tab="Before" +import { prisma } from './db'; + +// Find one +await prisma.user.findFirst({ where: { email } }); + +// Create +const user = await prisma.user.create({ data: { email, role: 'author' } }); + +// Update one +await prisma.user.update({ where: { id }, data: { role: 'author' } }); + +// Update many +await prisma.user.updateMany({ where: { role: 'reader' }, data: { role: 'author' } }); + +// Delete one +await prisma.user.delete({ where: { id } }); +``` + **Common mistakes:** | Don't | Do | Why | @@ -210,15 +224,26 @@ await db.orm.users.where({ _id: user._id }).delete(); ### 3b. Relations and polymorphism -```typescript +```typescript tab="After" +import { db } from './db'; + // Eager-load a relation -// v6: await prisma.post.findMany({ include: { author: true } }) const withAuthor = await db.orm.posts.include('author').all(); // Query one variant of a polymorphic collection const articles = await db.orm.posts.variant('Article').all(); ``` +```typescript tab="Before" +import { prisma } from './db'; + +// Eager-load a relation +const withAuthor = await prisma.post.findMany({ include: { author: true } }); + +// Query one variant of a polymorphic collection +const articles = await prisma.post.findMany({ where: { type: 'Article' } }); +``` + ### 3c. Aggregations v6's `groupBy` / `aggregate` become a chain of steps. `acc` provides the group totals (`acc.count()`, `acc.max(...)`, etc.) — the equivalent of v6's `_count` / `_sum` / `_max`. From a8fcfb687ae351404849faebd9dcd5c641161766 Mon Sep 17 00:00:00 2001 From: Raschid Jimenez Date: Mon, 27 Jul 2026 16:41:50 -0400 Subject: [PATCH 4/6] Improve sections 3e and beyond --- .../next/upgrade-prisma-orm/mongodb.mdx | 71 +++++++++++++------ 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx index fdbb185bfd..786d588f97 100644 --- a/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx +++ b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx @@ -306,7 +306,47 @@ await prisma.$transaction(async (tx) => { ### 3e. Raw operations -Use the pipeline builder for aggregations, or drop to the `mongodb` driver for arbitrary commands. +#### Aggregations + +The direct replacement for v6's `findRaw` and `aggregateRaw` is `db.raw`. + +```typescript tab="After" +import { db } from './db'; + +// Raw aggregation on a collection — replaces v6's aggregateRaw / findRaw. +const runtime = await db.runtime(); +const plan = db.raw + .collection('posts') + .aggregate([ ... ]) + .build(); +const rows = await runtime.execute(plan).toArray(); +``` + +```typescript tab="Before" +import { prisma } from './db'; + +// Raw aggregation on a collection. +const rows = await prisma.post.aggregateRaw({ + pipeline: [ ... ], +}); +``` + +#### Commands + +`db.raw` is collection-scoped, so it has no equivalent for v6's database-level `$runCommandRaw`. For arbitrary commands, construct your own `MongoClient`, pass it to the binding, and use that client directly — the same `db` object keeps giving you the typed ORM. + +```typescript tab="After" +import { MongoClient } from 'mongodb'; + +const mongoClient = new MongoClient(process.env['DATABASE_URL']); +await mongoClient.db('my-app-db').command({ ping: 1 }); +``` + +```typescript tab="Before" +import { prisma } from './db'; + +await prisma.$runCommandRaw({ ping: 1 }); +``` ### Quick reference table @@ -317,27 +357,19 @@ Use the pipeline builder for aggregations, or drop to the `mongodb` driver for a | `create` / `update` / `delete` / `upsert` | same names on `db.orm.` | | `updateMany` / `deleteMany` | `updateAll` / `deleteAll` | | `aggregate` / `groupBy` | `db.query.from(...).group(...).build()` → `runtime.execute(plan)` | -| `$runCommandRaw` / `findRaw` / `aggregateRaw` | pipeline builder, or the `mongodb` driver directly (arbitrary commands) | +| `findRaw` / `aggregateRaw` | `db.raw.collection(...).aggregate(...).build()` → `runtime.execute(plan)` | +| `$runCommandRaw` | `mongo({ mongoClient, dbName, contractJson })` — then `mongoClient.db(dbName).command(...)` | | `$transaction(...)` | No façade wrapper yet — use driver sessions on a replica set | | `$connect` / `$disconnect` | lazy connect on first use; `db.close()` to disconnect | ## 4. Adopt the migration lifecycle -v6 MongoDB had no Prisma Migrate (`db push` only). Prisma Next gives MongoDB first-class, contract-driven migrations. - -Unlike v6's `db push` (ephemeral, no history), Next migrations are: - -- **Reviewable**: diffs are packages you commit -- **Reproducible**: same package → same DB state across envs -- **Auditable**: migration history = schema evolution timeline - -For local dev, `db update` mimics `db push`. For shared/prod, use the migration workflow. +v6 MongoDB had no Prisma Migrate — only `db push` (ephemeral, no history). Prisma Next gives MongoDB first-class, contract-driven migrations: reviewable diffs you commit, reproducible across environments, and an auditable schema history. -The CLI reads the target from `prisma-next.config.ts`: +Use `db update` for local dev (the `db push` analogue) and the migration workflow for shared/prod. The CLI reads the target from `prisma-next.config.ts`: ```bash -# Local dev: diff the contract against the live DB and apply directly (no history kept), -# the closest analogue to the old `db push`. Mongo dev scaffolds often need ?replicaSet=rs0. +# Local dev: diff the contract against the live DB and apply directly, no history kept. npx prisma-next contract emit npx prisma-next db update --db "$DATABASE_URL" @@ -349,17 +381,14 @@ npx prisma-next db verify --db "$DATABASE_URL" # check the DB matches the cont **Good to know:** -- You never hand-write migration steps. You declare indexes and validators in the contract (step 2), and `migration plan` works out the changes for you. -- On a database Prisma Next hasn't managed before, run `db init` once first. If you ever fix something by hand, run `db sign` so Prisma Next knows the current state. -- If a `migrate` run gets interrupted, just run it again. It picks up where it left off. Run `db verify` any time to check the database still matches your contract. -- By default, Prisma Next adds strict server-side validators to your collections using MongoDB's `$jsonSchema`. Before running this in production, make sure your existing documents pass them. +- You never hand-write migration steps — declare indexes and validators in the contract (step 2) and `migration plan` derives the changes. +- Run `db init` once on a database Prisma Next hasn't managed before, and `db sign` after any manual fix. Interrupted `migrate` runs are resumable — just rerun. +- Prisma Next adds strict `$jsonSchema` validators by default; make sure existing documents pass them before running in production. ## 5. Pre-flight checklist (before production cutover) Work through this list before you switch production traffic over: -- [ ] Your Prisma Next config points at the **same** database and connection string as v6. -- [ ] Your MongoDB server is on 8.0 or newer. - [ ] **Dry run first.** Rehearse the whole flow on a throwaway copy of your database (`contract emit`, `migration plan`, `migrate`, `db verify`) and make sure `db verify` passes before you touch production. - [ ] **Index parity.** The indexes on each collection (`db.collection.getIndexes()`) match your contract. - [ ] **Validators.** Your existing documents pass the strict validators Prisma Next adds to each collection (see *Good to know* in step 4). @@ -375,7 +404,7 @@ Don't delete the v6 client, schema, or dependencies until this checklist passes. Once you've cut over, this is the day-to-day workflow: -- **Schema changes.** Edit the contract, then run `contract emit`, `migration plan`, and `migrate` (the same loop from step 4). +- **Schema changes.** Edit the contract, then run `contract emit`, `migration plan`, and `migrate` (same loop from step 4). - **Troubleshooting.** `db verify` checks the database against your contract, and `db sign` records any fixes you make by hand. - **Performance.** Your aggregations now compile to native MongoDB pipelines, so it's worth benchmarking them against your old v6 raw calls. From 0497484bfc383855ed61898ce6447bdd7fae4302 Mon Sep 17 00:00:00 2001 From: Raschid Jimenez Date: Mon, 27 Jul 2026 16:56:00 -0400 Subject: [PATCH 5/6] remove leftover md file --- MIGRATION.md | 379 --------------------------------------------------- 1 file changed, 379 deletions(-) delete mode 100644 MIGRATION.md diff --git a/MIGRATION.md b/MIGRATION.md deleted file mode 100644 index eeb9fb6fa5..0000000000 --- a/MIGRATION.md +++ /dev/null @@ -1,379 +0,0 @@ -# Migrating a MongoDB project from Prisma v6 to Prisma Next - -This guide is for developers moving a Prisma ORM v6 MongoDB project to -[Prisma Next](https://github.com/prisma/prisma-next). Prisma 7 has **no MongoDB connector**, so -Prisma Next is the successor path. This is a **code and workflow migration against the same -database** — your data never moves. - -> [!TIP] -> **Working with an AI coding agent?** Install the companion `prisma-mongodb-upgrade` skill with `npx skills add prisma/skills` to help your agent with this migration. - -## Prerequisites - -- **Node.js 24+** -- **TypeScript 5.9+** -- **MongoDB 8.0+** -- **The `mongodb` node driver 7.x** is a required peer dependency -- **A replica set** if your app uses transactions. Prisma Next's ORM doesn't wrap `$transaction` yet, so multi-document atomicity goes through the driver's sessions (see step 3d), which MongoDB only allows on a replica set. - -> Prisma Next MongoDB is Early Access (GA planned after Postgres), so details can shift between -> releases — check anything below against the `@prisma-next/*` version you install. - -## Migration Overview - -- [ ] **Set up**: scaffold Next and add the MongoDB driver. -- [ ] **Port the schema**: convert your v6 models to a Prisma Next contract. -- [ ] **Port the code**: update client calls, transactions, and aggregations. -- [ ] **Adopt migrations**: replace the `db push` workflow. -- [ ] **Verify**: check index parity and validators, then soak-test reads. -- [ ] **Cut over**: a code-only switch; your data never moves. - -## 1. Set up Prisma Next - -Scaffold the project, then add the MongoDB driver as a peer dependency: - -```bash -npx prisma-next init --yes --target mongodb --authoring psl -``` - -`init` writes a `prisma-next.config.ts` at the repo root. MongoDB is selected by importing the -`@prisma-next/mongo` façade, not by a `provider` string in the schema. Point the connection -at the **same database** your v6 app uses. - -**Before:** - -```prisma -// schema.prisma -datasource db { - provider = "mongodb" - url = env("DATABASE_URL") -} - -// Rest of schema... -``` - -**After:** - -```typescript -// prisma-next.config.ts -import { defineConfig } from '@prisma-next/mongo/config'; - -export default defineConfig({ - contract: './src/prisma/contract.prisma', - db: { - connection: process.env['DATABASE_URL'], - }, -}); -``` - -> [!IMPORTANT] -> **MONGODB-AWS auth only:** the `mongodb@^7` driver no longer accepts AWS credentials embedded in the connection string. See the [driver v7 breaking changes](https://github.com/mongodb/node-mongodb-native/blob/HEAD/etc/notes/CHANGES_7.0.0.md#-aws-authentication). - -### Import the client - -The way to instantiate the Prisma client now is from the emitted contract: - -**Before:** - -```typescript -// src/prisma/db.ts -import { PrismaClient } from '../generated/prisma/client'; - -const prisma = new PrismaClient(); - -export { prisma }; -``` - -**After:** - -```typescript -// src/prisma/db.ts -import mongo from '@prisma-next/mongo/runtime'; -import type { Contract } from './contract'; -import contractJson from './contract.json' with { type: 'json' }; - -const db = mongo({ - contractJson, - url: process.env['DATABASE_URL'], - dbName: 'my-app-db', -}); - -export { db }; -``` - -### Generate the contract - -Run `npx prisma-next contract emit` to generate `contract.json` from your `.prisma` contract file. Re-run this whenever you change the contract. - -## 2. Port the Schema to a Contract - -> [!IMPORTANT] -> Prisma Next addresses collections by **storage name**, not model name: `db.orm.users` (the `@@map` name, or the lowercased model name), never `db.orm.User`. Whatever you `@@map` in this step is the exact name every client call uses in step 3. - -The v6 `schema.prisma` is not consumed as-is; it becomes a **contract**, authored in PSL (shown -here) or TypeScript. The syntax is close to v6, with a few differences. - -In the table below, the left column is how you wrote it in v6 and the right column is the Prisma Next equivalent: - -| Concept | v6 (`schema.prisma`) | Prisma Next (`contract.prisma`) | -|---------|----------------------|---------------------------------| -| Datasource | `datasource db { provider = "mongodb" }` | no `provider` (configured in `prisma-next.config.ts`) | -| Id field | `id String @id @default(auto()) @map("_id") @db.ObjectId` | `id ObjectId @id @map("_id")` | -| Embedded document | `type Address { ... }` | `type Address { ... }` (unchanged) | -| Index | `@@index(...)`, synced by `db push` | `@@index(...)` / `@@unique(...)`, applied by migrations | -| Polymorphism | not supported | `@@discriminator(field)` on the base + `@@base(Base, "value")` on each variant.
See the [Base Model and Variants](https://www.prisma.io/docs/orm/next/contract-authoring/psl-syntax#base-models-and-variants) | - -A fuller contract putting these together — enum, embedded type, relation, and polymorphism: - -```prisma -// src/prisma/contract.prisma -enum UserRole { - @@type("mongo/string@1") - Admin = "admin" - Author = "author" - Reader = "reader" -} - -type Address { - street String - city String - country String -} - -model User { - id ObjectId @id @map("_id") - email String - role UserRole - address Address? - posts Post[] - @@map("users") -} - -model Post { - id ObjectId @id @map("_id") - title String - kind String - authorId ObjectId - createdAt DateTime - author User @relation(fields: [authorId], references: [id]) - @@discriminator(kind) - @@index([createdAt(sort: Desc), authorId]) - @@map("posts") -} - -model Article { - summary String - @@base(Post, "article") -} -``` - -## 3. Port client calls - -Names look similar but parity does not hold. This section breaks down each category of operation. - -### 3a. Basic CRUD - -Prisma Next uses a chained API instead of v6's single options object: you start from a collection -(like `db.orm.users`), add filters and options step by step, then finish with a method that runs -the query. Each example shows the v6 call first, then its Prisma Next equivalent (see **Common -mistakes** below for the two rules that trip people up most): - -```typescript -import { db } from './db'; - -// Find one -// v6: await prisma.user.findFirst({ where: { email } }) -await db.orm.users.where({ email: 'alice@example.com' }).first(); - -// Create -// v6: await prisma.user.create({ data: { email, role: 'author' } }) -const user = await db.orm.users.create({ email: 'a@e.com', role: 'author', address: null }); - -// Update one -// v6: await prisma.user.update({ where: { id }, data: { role: 'author' } }) -await db.orm.users.where({ _id: user._id }).update({ role: 'author' }); - -// Update many -// v6: await prisma.user.updateMany({ where: { role: 'reader' }, data: { role: 'author' } }) -await db.orm.users.where({ role: 'reader' }).updateAll({ role: 'author' }); - -// Delete one -// v6: await prisma.user.delete({ where: { id } }) -await db.orm.users.where({ _id: user._id }).delete(); -``` - -**Common mistakes:** - -| ❌ Don't | ✅ Do | Why | -|---------|------|-----| -| `db.orm.User` | `db.orm.users` | address by storage name, not model name | -| `.where({ email: { equals: 'x' } })` | `.where({ email: 'x' })` | `.where(...)` takes a plain equality object | -| `.update({ ... })` with no `.where(...)` | `.where(...).update({ ... })` | every mutation except `create` needs a `.where(...)` first | - -### 3b. Relations and polymorphism - -```typescript -// Eager-load a relation -// v6: await prisma.post.findMany({ include: { author: true } }) -const withAuthor = await db.orm.posts.include('author').all(); - -// Query one variant of a polymorphic collection -const articles = await db.orm.posts.variant('Article').all(); -``` - -### 3c. Aggregations - -v6's `groupBy` / `aggregate` become a chain of steps. `acc` provides the group totals -(`acc.count()`, `acc.max(...)`, etc.) — the equivalent of v6's `_count` / `_sum` / `_max`. - -Before: - -```typescript -await prisma.post.groupBy({ - by: ['kind'], - where: { authorId }, - _count: { _all: true }, - _max: { createdAt: true }, - orderBy: { _count: { kind: 'desc' } }, -}); -``` - -After: - -```typescript -import { acc } from '@prisma-next/mongo-query-builder'; - -const runtime = await db.runtime(); -const plan = db.query - .from('posts') - .match((f) => f.authorId.eq(authorId)) - .group((f) => ({ _id: f.kind, postCount: acc.count(), latest: acc.max(f.createdAt) })) - .sort({ postCount: -1 }) - .build(); -const byKind = await runtime.execute(plan); -``` - -### 3d. Transactions - -Prisma Next has no `transaction` method for MongoDB yet. For multi-document transactions, use the `mongodb` driver's [Transaction API](https://www.mongodb.com/docs/drivers/node/current/crud/transactions/transaction-conv/). - -**Before:** - -```typescript -await prisma.$transaction(async (tx) => { - // ...writes... -}); -``` - -**After:** - -```typescript -import { MongoClient } from 'mongodb'; - -const client = new MongoClient(DATABASE_URL); -const session = client.startSession(); -const mdb = client.db('my-app-db'); - -try { - await session.withTransaction(async () => { - // Pass { session } to EVERY operation — an op without it silently runs - // outside the transaction. - await mdb - .collection('users') - .insertOne({ email: 'a@e.com', role: 'author' }, { session }); - await mdb - .collection('posts') - .updateOne({ _id: postId }, { $set: { authorId } }, { session }); - }); -} finally { - await session.endSession(); -} -``` - -### 3e. Raw operations - -Use the pipeline builder for aggregations, or drop to the `mongodb` driver for arbitrary commands. - -### Quick reference table - -| v6 | Prisma Next | -|----|-------------| -| `prisma.user.findMany(...)` | `db.orm.users.where({ ... }).all()` | -| `prisma.user.findFirst(...)` | `db.orm.users.where({ ... }).first()` | -| `create` / `update` / `delete` / `upsert` | same names on `db.orm.` | -| `updateMany` / `deleteMany` | `updateAll` / `deleteAll` | -| `aggregate` / `groupBy` | `db.query.from(...).group(...).build()` → `runtime.execute(plan)` | -| `$runCommandRaw` / `findRaw` / `aggregateRaw` | pipeline builder, or the `mongodb` driver directly (arbitrary commands) | -| `$transaction(...)` | No façade wrapper yet — use driver sessions on a replica set | -| `$connect` / `$disconnect` | lazy connect on first use; `db.close()` to disconnect | - -## 4. Adopt the migration lifecycle - -v6 MongoDB had no Prisma Migrate (`db push` only). Prisma Next gives MongoDB first-class, -contract-driven migrations. - -Unlike v6's `db push` (ephemeral, no history), Next migrations are: -- **Reviewable**: diffs are packages you commit -- **Reproducible**: same package → same DB state across envs -- **Auditable**: migration history = schema evolution timeline - -For local dev, `db update` mimics `db push`. For shared/prod, use the migration workflow. - -The CLI reads the target from `prisma-next.config.ts`: - -```bash -# Local dev: diff the contract against the live DB and apply directly (no history kept), -# the closest analogue to the old `db push`. Mongo dev scaffolds often need ?replicaSet=rs0. -npx prisma-next contract emit -npx prisma-next db update --db "$DATABASE_URL" - -# Shared branches / production: write a reviewable migration package, then apply it. -npx prisma-next migration plan --name add_posts_indexes -npx prisma-next migrate --db "$DATABASE_URL" -npx prisma-next db verify --db "$DATABASE_URL" # check the DB matches the contract -``` - -**Good to know:** - -- You never hand-write migration steps. You declare indexes and validators in the contract - (step 2), and `migration plan` works out the changes for you. -- On a database Prisma Next hasn't managed before, run `db init` once first. If you ever fix - something by hand, run `db sign` so Prisma Next knows the current state. -- If a `migrate` run gets interrupted, just run it again. It picks up where it left off. Run - `db verify` any time to check the database still matches your contract. -- By default, Prisma Next adds strict server-side validators to your collections using MongoDB's `$jsonSchema`. Before running this in - production, make sure your existing documents pass them. - -## 5. Pre-flight checklist (before production cutover) - -Work through this list before you switch production traffic over: - -- [ ] Your Prisma Next config points at the **same** database and connection string as v6. -- [ ] Your MongoDB server is on 8.0 or newer. -- [ ] **Dry run first.** Rehearse the whole flow on a throwaway copy of your database (`contract - emit`, `migration plan`, `migrate`, `db verify`) and make sure `db verify` passes before you - touch production. -- [ ] **Index parity.** The indexes on each collection (`db.collection.getIndexes()`) match your - contract. -- [ ] **Validators.** Your existing documents pass the strict validators Prisma Next adds to each - collection (see *Good to know* in step 4). -- [ ] **Addressing.** Every call site uses storage names like `db.orm.users`, not model names. -- [ ] **Transactions.** Every v6 `$transaction` now has a driver-session equivalent. -- [ ] **Raw calls.** Every `$runCommandRaw`, `findRaw`, and `aggregateRaw` has a pipeline-builder - or `mongodb`-driver replacement. -- [ ] **Read-only trial run.** Run Prisma Next read-only alongside your v6 app for a while and - compare the results. This surfaces any differences while v6 is still handling all the writes. -- [ ] **Cutover and rollback.** Switch writes over only once the trial run looks good, and keep the - v6 branch deployable. Rolling back is just a code rollback since your data never moved. - -Don't delete the v6 client, schema, or dependencies until this checklist passes. - -## 6. Post-migration: adopting Next workflows - -Once you've cut over, this is the day-to-day workflow: - -- **Schema changes.** Edit the contract, then run `contract emit`, `migration plan`, and `migrate` (the same loop from step 4). -- **Troubleshooting.** `db verify` checks the database against your contract, and `db sign` records any fixes you make by hand. -- **Performance.** Your aggregations now compile to native MongoDB pipelines, so it's worth benchmarking them against your old v6 raw calls. - -See [Prisma Next docs](https://github.com/prisma/prisma-next) for pipeline builder patterns, relation loading strategies, and advanced contract features. This guide gets you across the bridge; Prisma Next's docs are the source of truth from here on. From 50e24b11d81bfb049e68f0425cd55afe73976087 Mon Sep 17 00:00:00 2001 From: Ankur Datta <64993082+ankur-arch@users.noreply.github.com> Date: Tue, 28 Jul 2026 14:25:01 +0200 Subject: [PATCH 6/6] Validate guide against prisma-next 0.16.0; fix adoption flow, ObjectId filters, and raw-command mapping Every command and snippet now runs against a live v6 database on mongodb-memory-server (mongod 8.0.4 replica set) migrated to @prisma-next/mongo@0.16.0. Changes that came out of that run: - Step 4: db init refuses a populated v6 database (validator adds are classed destructive), and migration plans start from an empty baseline without a ref, so the adoption path is db update --advance-ref db, with plan/migrate/verify for every change after that. - 3c: the typed pipeline builder passes ObjectId values through as strings, so match on an ObjectId field silently returns nothing; grouped example no longer filters by authorId and a warning shows the working rawCommand + ObjectId pattern. - 3e/quick reference: $runCommandRaw maps to sharing one MongoClient with the mongo() binding (mongoClient + dbName), shown end to end; collection-scoped escapes point at db.query.rawCommand. - Checklist: restore same-database and MongoDB 8.0+ items; rehearsal commands now match the adoption flow. - Step 2: note that every discriminator value in existing data needs a declared variant with at least one field. - Align snippets with the actual init scaffold (contract.d import, non-null env assertions), storage-value enum row in common mistakes, relative docs links, em-dash cleanup. Co-Authored-By: Claude Fable 5 --- .../next/upgrade-prisma-orm/mongodb.mdx | 115 ++++++++++++------ 1 file changed, 78 insertions(+), 37 deletions(-) diff --git a/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx index 786d588f97..0565e641cf 100644 --- a/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx +++ b/apps/docs/content/docs/guides/next/upgrade-prisma-orm/mongodb.mdx @@ -6,7 +6,7 @@ metaTitle: How to migrate a MongoDB project from Prisma v6 to Prisma Next metaDescription: Step-by-step guide to migrating a Prisma ORM v6 MongoDB project to Prisma Next, covering setup, contracts, client calls, migrations, and production cutover. --- -This guide is for developers moving a Prisma ORM v6 MongoDB project to **Prisma Next**. Prisma 7 has **no MongoDB connector**, so Prisma Next is the successor path. This is a **code and workflow migration against the same database** — your data never moves. +This guide is for developers moving a Prisma ORM v6 MongoDB project to **Prisma Next**. Prisma 7 has **no MongoDB connector**, so Prisma Next is the successor path. This is a **code and workflow migration against the same database**: your data never moves. :::tip[Working with an AI coding agent?] @@ -24,7 +24,7 @@ Install the companion [prisma-mongodb-upgrade](https://github.com/prisma/skills/ :::info -Prisma Next MongoDB is Early Access (GA planned after Postgres), so details can shift between releases — check anything below against the `@prisma-next/*` version you install. +Prisma Next MongoDB is Early Access (GA planned after Postgres) and evolves quickly, so check anything below against the `@prisma-next/*` version you install. Every command and code example in this guide was validated against `@prisma-next/mongo@0.16.0`. ::: @@ -46,12 +46,13 @@ npx prisma-next init --yes --target mongodb --authoring psl ```typescript tab="After" // prisma-next.config.ts +import 'dotenv/config'; import { defineConfig } from '@prisma-next/mongo/config'; export default defineConfig({ contract: './src/prisma/contract.prisma', db: { - connection: process.env['DATABASE_URL'], + connection: process.env['DATABASE_URL']!, }, }); ``` @@ -77,12 +78,12 @@ The way to instantiate the Prisma client now is from the emitted contract: ```typescript tab="After" // src/prisma/db.ts import mongo from '@prisma-next/mongo/runtime'; -import type { Contract } from './contract'; +import type { Contract } from './contract.d'; import contractJson from './contract.json' with { type: 'json' }; const db = mongo({ contractJson, - url: process.env['DATABASE_URL'], + url: process.env['DATABASE_URL']!, dbName: 'my-app-db', }); @@ -98,7 +99,7 @@ const prisma = new PrismaClient(); export { prisma }; ``` -## 2. Port the Schema to a Contract +## 2. Port the schema to a contract :::note @@ -122,7 +123,7 @@ You can use the [prisma-mongodb-upgrade](https://github.com/prisma/skills/tree/m | Id field | `id String @id @default(auto()) @map("_id") @db.ObjectId` | `id ObjectId @id @map("_id")` | | Embedded document | `type Address { ... }` | `type Address { ... }` (unchanged) | | Index | `@@index(...)`, synced by `db push` | `@@index(...)` / `@@unique(...)`, applied by migrations | -| Polymorphism | not supported | `@@discriminator(field)` on the base + `@@base(Base, "value")` on each variant.
See the [Base Model and Variants](https://www.prisma.io/docs/orm/next/contract-authoring/psl-syntax#base-models-and-variants) | +| Polymorphism | not supported | `@@discriminator(field)` on the base + `@@base(Base, "value")` on each variant.
See [Base models and variants](/orm/next/contract-authoring/psl-syntax#base-models-and-variants) | A fuller contract putting together enum, embedded type, relation, and polymorphism: @@ -168,6 +169,8 @@ model Article { } ``` +When you use `@@discriminator`, declare a variant for every value the field takes in your existing data: the generated validator only accepts declared values, so writes to documents with an undeclared value fail once the validators are live. A variant model must declare at least one field (an optional one is enough). + ## 3. Port client calls Names look similar but parity does not hold. This section breaks down each category of operation. @@ -221,6 +224,7 @@ await prisma.user.delete({ where: { id } }); | `db.orm.User` | `db.orm.users` | address by storage name, not model name | | `.where({ email: { equals: 'x' } })` | `.where({ email: 'x' })` | `.where(...)` takes a plain equality object | | `.update({ ... })` with no `.where(...)` | `.where(...).update({ ... })` | every mutation except `create` needs a `.where(...)` first | +| `role: 'Author'` | `role: 'author'` | enums read and write their storage value, not the key name | ### 3b. Relations and polymorphism @@ -241,36 +245,53 @@ import { prisma } from './db'; const withAuthor = await prisma.post.findMany({ include: { author: true } }); // Query one variant of a polymorphic collection -const articles = await prisma.post.findMany({ where: { type: 'Article' } }); +const articles = await prisma.post.findMany({ where: { kind: 'article' } }); ``` ### 3c. Aggregations -v6's `groupBy` / `aggregate` become a chain of steps. `acc` provides the group totals (`acc.count()`, `acc.max(...)`, etc.) — the equivalent of v6's `_count` / `_sum` / `_max`. +v6's `groupBy` / `aggregate` become a typed pipeline: build a plan with `db.query`, then run it with `db.execute`. `acc` provides the group totals (`acc.count()`, `acc.max(...)`, and friends), the equivalent of v6's `_count` / `_sum` / `_max`. ```typescript tab="After" import { acc } from '@prisma-next/mongo-query-builder'; -const runtime = await db.runtime(); const plan = db.query .from('posts') - .match((f) => f.authorId.eq(authorId)) .group((f) => ({ _id: f.kind, postCount: acc.count(), latest: acc.max(f.createdAt) })) .sort({ postCount: -1 }) .build(); -const byKind = await runtime.execute(plan); +const byKind = await db.execute(plan).toArray(); ``` ```typescript tab="Before" await prisma.post.groupBy({ by: ['kind'], - where: { authorId }, _count: { _all: true }, _max: { createdAt: true }, orderBy: { _count: { kind: 'desc' } }, }); ``` +:::warning[ObjectId filters need a raw command] + +Don't add `.match((f) => f.authorId.eq(authorId))` to filter by an `ObjectId` field (`_id`, or a foreign key like `authorId`): the typed builder sends the id through as a plain string, so the stage silently matches nothing. Filter ObjectId fields with the ORM client instead (`db.orm.posts.where({ authorId })` encodes them for you), or run the aggregation as a raw command carrying a real `ObjectId`: + +```typescript +import { RawAggregateCommand } from '@prisma-next/mongo-query-ast/execution'; +import { ObjectId } from 'mongodb'; + +const plan = db.query.rawCommand( + new RawAggregateCommand('posts', [ + { $match: { authorId: new ObjectId(String(authorId)) } }, + { $group: { _id: '$kind', postCount: { $count: {} }, latest: { $max: '$createdAt' } } }, + { $sort: { postCount: -1 } }, + ]), +); +const byKind = await db.execute(plan).toArray(); +``` + +::: + ### 3d. Transactions Prisma Next has no `transaction` method for MongoDB yet. For multi-document transactions, use the `mongodb` driver's [Transaction API](https://www.mongodb.com/docs/drivers/node/current/crud/transactions/transaction-conv/). @@ -278,14 +299,14 @@ Prisma Next has no `transaction` method for MongoDB yet. For multi-document tran ```typescript tab="After" import { MongoClient } from 'mongodb'; -const client = new MongoClient(DATABASE_URL); +const client = new MongoClient(process.env['DATABASE_URL']!); const session = client.startSession(); const mdb = client.db('my-app-db'); try { await session.withTransaction(async () => { - // Pass { session } to EVERY operation — an op without it silently runs - // outside the transaction. + // Pass { session } to EVERY operation: an op without it silently + // runs outside the transaction. await mdb .collection('users') .insertOne({ email: 'a@e.com', role: 'author' }, { session }); @@ -313,13 +334,12 @@ The direct replacement for v6's `findRaw` and `aggregateRaw` is `db.raw`. ```typescript tab="After" import { db } from './db'; -// Raw aggregation on a collection — replaces v6's aggregateRaw / findRaw. -const runtime = await db.runtime(); +// Raw aggregation on a collection, replaces v6's aggregateRaw / findRaw. const plan = db.raw .collection('posts') .aggregate([ ... ]) .build(); -const rows = await runtime.execute(plan).toArray(); +const rows = await db.execute(plan).toArray(); ``` ```typescript tab="Before" @@ -333,12 +353,20 @@ const rows = await prisma.post.aggregateRaw({ #### Commands -`db.raw` is collection-scoped, so it has no equivalent for v6's database-level `$runCommandRaw`. For arbitrary commands, construct your own `MongoClient`, pass it to the binding, and use that client directly — the same `db` object keeps giving you the typed ORM. +`db.raw` is collection-scoped, so v6's database-level `$runCommandRaw` has no ORM equivalent. Construct your own `MongoClient` and pass it to the binding from step 1 in place of `url`: the same client then powers the typed ORM and runs arbitrary commands, sharing one connection pool. ```typescript tab="After" +// src/prisma/db.ts +import mongo from '@prisma-next/mongo/runtime'; import { MongoClient } from 'mongodb'; +import type { Contract } from './contract.d'; +import contractJson from './contract.json' with { type: 'json' }; + +export const mongoClient = new MongoClient(process.env['DATABASE_URL']!); +export const db = mongo({ contractJson, mongoClient, dbName: 'my-app-db' }); -const mongoClient = new MongoClient(process.env['DATABASE_URL']); +// Elsewhere: typed ORM and raw commands from the same client. +await db.orm.users.where({ email: 'alice@example.com' }).first(); await mongoClient.db('my-app-db').command({ ping: 1 }); ``` @@ -348,6 +376,8 @@ import { prisma } from './db'; await prisma.$runCommandRaw({ ping: 1 }); ``` +For raw reads and writes scoped to a single collection (anything the typed builder can't express), you don't need the driver: `db.query.rawCommand(...)` packages a raw command into a plan, as shown in [3c](#3c-aggregations). See [Raw queries](/orm/next/reference/raw-queries) for the full surface. + ### Quick reference table | v6 | Prisma Next | @@ -356,40 +386,51 @@ await prisma.$runCommandRaw({ ping: 1 }); | `prisma.user.findFirst(...)` | `db.orm.users.where({ ... }).first()` | | `create` / `update` / `delete` / `upsert` | same names on `db.orm.` | | `updateMany` / `deleteMany` | `updateAll` / `deleteAll` | -| `aggregate` / `groupBy` | `db.query.from(...).group(...).build()` → `runtime.execute(plan)` | -| `findRaw` / `aggregateRaw` | `db.raw.collection(...).aggregate(...).build()` → `runtime.execute(plan)` | -| `$runCommandRaw` | `mongo({ mongoClient, dbName, contractJson })` — then `mongoClient.db(dbName).command(...)` | -| `$transaction(...)` | No façade wrapper yet — use driver sessions on a replica set | +| `aggregate` / `groupBy` | `db.query.from(...).group(...).build()` → `db.execute(plan)` | +| `findRaw` / `aggregateRaw` | `db.raw.collection(...).aggregate(...).build()` → `db.execute(plan)`, or `db.query.rawCommand(...)` | +| `$runCommandRaw` | share a `MongoClient` with the binding, then `mongoClient.db(...).command(...)` (see 3e) | +| `$transaction(...)` | no wrapper yet; driver sessions on a replica set (see 3d) | | `$connect` / `$disconnect` | lazy connect on first use; `db.close()` to disconnect | ## 4. Adopt the migration lifecycle -v6 MongoDB had no Prisma Migrate — only `db push` (ephemeral, no history). Prisma Next gives MongoDB first-class, contract-driven migrations: reviewable diffs you commit, reproducible across environments, and an auditable schema history. +v6 MongoDB had no Prisma Migrate, only `db push` (ephemeral, no history). Prisma Next gives MongoDB first-class, contract-driven migrations: reviewable diffs you commit, reproducible across environments, and an auditable schema history. + +### Bring the v6 database under management -Use `db update` for local dev (the `db push` analogue) and the migration workflow for shared/prod. The CLI reads the target from `prisma-next.config.ts`: +Your database already has collections and data, so bootstrap it with `db update`: it diffs the live database against your contract, applies the delta (the strict validators, plus any indexes the contract declares that v6 never created), and signs the database so Prisma Next recognizes it from then on. The `--advance-ref db` flag records the resulting contract state as the [`db` ref](/orm/next/migrations/the-migration-graph#name-important-states-with-refs), the starting point later migration plans diff from. ```bash -# Local dev: diff the contract against the live DB and apply directly, no history kept. npx prisma-next contract emit -npx prisma-next db update --db "$DATABASE_URL" +npx prisma-next db update --db "$DATABASE_URL" --dry-run # preview the delta first +npx prisma-next db update --db "$DATABASE_URL" --advance-ref db +``` + +Don't reach for `db init` here. It bootstraps only additively, and on a populated database it refuses the validator changes this migration needs, because adding a validator to a collection with existing documents is classed destructive. `db update` applies them after a confirmation prompt. + +### Every schema change after that -# Shared branches / production: write a reviewable migration package, then apply it. -npx prisma-next migration plan --name add_posts_indexes -npx prisma-next migrate --db "$DATABASE_URL" +Use `db update` for local experiments (the `db push` analogue, no history kept) and the migration workflow for shared branches and production: + +```bash +npx prisma-next migration plan --name add_posts_indexes # plans from the db ref +npx prisma-next migrate --db "$DATABASE_URL" --advance-ref db npx prisma-next db verify --db "$DATABASE_URL" # check the DB matches the contract ``` **Good to know:** -- You never hand-write migration steps — declare indexes and validators in the contract (step 2) and `migration plan` derives the changes. -- Run `db init` once on a database Prisma Next hasn't managed before, and `db sign` after any manual fix. Interrupted `migrate` runs are resumable — just rerun. -- Prisma Next adds strict `$jsonSchema` validators by default; make sure existing documents pass them before running in production. +- You never hand-write migration steps: declare indexes and validators in the contract (step 2) and `migration plan` derives the changes. +- Interrupted `migrate` runs are resumable; just rerun. After fixing anything by hand, run `db sign` so the signature matches the database again. +- Prisma Next adds strict `$jsonSchema` validators by default. Make sure existing documents pass them before running in production: once the validators are live, writes to documents that don't match the contract fail with `Document failed validation`. ## 5. Pre-flight checklist (before production cutover) Work through this list before you switch production traffic over: -- [ ] **Dry run first.** Rehearse the whole flow on a throwaway copy of your database (`contract emit`, `migration plan`, `migrate`, `db verify`) and make sure `db verify` passes before you touch production. +- [ ] **Same database.** Your Prisma Next config points at the **same** database and connection string as v6. +- [ ] **Server version.** Your MongoDB server is on 8.0 or newer. +- [ ] **Dry run first.** Rehearse the whole flow on a throwaway copy of your database (`contract emit`, `db update --dry-run`, `db update`, `db verify`) and make sure `db verify` passes before you touch production. - [ ] **Index parity.** The indexes on each collection (`db.collection.getIndexes()`) match your contract. - [ ] **Validators.** Your existing documents pass the strict validators Prisma Next adds to each collection (see *Good to know* in step 4). - [ ] **Addressing.** Every call site uses storage names like `db.orm.users`, not model names. @@ -408,4 +449,4 @@ Once you've cut over, this is the day-to-day workflow: - **Troubleshooting.** `db verify` checks the database against your contract, and `db sign` records any fixes you make by hand. - **Performance.** Your aggregations now compile to native MongoDB pipelines, so it's worth benchmarking them against your old v6 raw calls. -See [Prisma Next docs](https://github.com/prisma/prisma-next) for pipeline builder patterns, relation loading strategies, and advanced contract features. This guide gets you across the bridge; Prisma Next's docs are the source of truth from here on. +See the [Prisma Next docs](/orm/next) for [pipeline builder patterns](/orm/next/reference/pipeline-builder), [relation loading strategies](/orm/next/reference/orm-client), and [advanced contract features](/orm/next/contract-authoring/psl-syntax). This guide gets you across the bridge; Prisma Next's docs are the source of truth from here on.