prisma db pull, convert Mongoose schemas to Prisma models (mapping _id with @map("_id") @db.ObjectId), then incrementally replace Mongoose queries with type-safe Prisma Client calls.npx prisma db pull (introspects existing MongoDB into a Prisma schema)prisma migrate) does not support MongoDB — always use prisma db push to sync schema changes. Also: Prisma v7 does not yet support MongoDB — stay on v6.19.prisma migrate dev and prisma migrate deploy are not supported for MongoDB — always use prisma db push to sync schema changes. [src3]mongod will fail on any prisma.$transaction() call. [src3]prisma db pull) should be run once for initial schema — repeated runs overwrite custom relations, composite types, and @ignore attributes. [src1]PrismaClient per request — use a singleton; each new PrismaClient() opens a fresh connection pool. [src2]| Mongoose Pattern | Prisma Equivalent | Example |
|---|---|---|
new Schema({ name: String }) | model User { name String } | Define in schema.prisma instead of JS |
mongoose.model('User', schema) | prisma.user | Auto-generated from schema, no manual model registration |
User.create({ name }) | prisma.user.create({ data: { name } }) | Wrap fields in data: object |
User.findById(id) | prisma.user.findUnique({ where: { id } }) | Use where: clause with unique field |
User.find({ age: { $gt: 18 } }) | prisma.user.findMany({ where: { age: { gt: 18 } } }) | Replace $gt with gt (no dollar prefix) |
User.findOneAndUpdate(filter, update) | prisma.user.update({ where, data }) | Separate where and data arguments |
User.deleteOne({ _id: id }) | prisma.user.delete({ where: { id } }) | Use id not _id (mapped via @map) |
User.find().populate('posts') | prisma.user.findMany({ include: { posts: true } }) | Type-safe eager loading via include |
User.find().select('name email') | prisma.user.findMany({ select: { name: true, email: true } }) | Boolean field selection |
User.find().sort({ name: 1 }) | prisma.user.findMany({ orderBy: { name: 'asc' } }) | Replace 1/-1 with 'asc'/'desc' |
User.find().skip(10).limit(5) | prisma.user.findMany({ skip: 10, take: 5 }) | limit becomes take |
User.countDocuments(filter) | prisma.user.count({ where: filter }) | Same filter syntax as findMany |
Schema.pre('save', fn) | Prisma Client extensions ($extends) | Use $extends — $use() middleware removed in Prisma 7 |
subdoc: { type: AddressSchema } | type Address { ... } + address Address | Use type keyword for embedded docs |
START
├── Is your MongoDB running as a replica set?
│ ├── NO → Enable replica set first (required for Prisma transactions)
│ └── YES ↓
├── Are you on Prisma v7?
│ ├── YES → Prisma v7 doesn't support MongoDB yet — downgrade to v6.19
│ └── NO (v6.x) ↓
├── Do you need Prisma Migrate (SQL-style migrations)?
│ ├── YES → Prisma Migrate doesn't support MongoDB — use prisma db push instead
│ └── NO ↓
├── Does your Mongoose schema use middleware (pre/post hooks)?
│ ├── YES → Convert to Prisma Client extensions ($extends) — not $use() (removed in v7)
│ └── NO ↓
├── Does your schema use embedded subdocuments?
│ ├── YES → Convert to Prisma composite types (use type keyword in schema)
│ └── NO ↓
├── Does your schema use discriminators (polymorphic models)?
│ ├── YES → Prisma doesn't support discriminators — flatten into separate models
│ └── NO ↓
└── DEFAULT → Introspect with prisma db pull, add relations manually, replace queries incrementally
Add Prisma to your existing project without removing Mongoose. Both ORMs can coexist and query the same database during migration. [src1]
npm install @prisma/client@6
npm install -D prisma@6
npx prisma init --datasource-provider mongodb
Verify: ls prisma/schema.prisma exists and contains provider = "mongodb".
Set your MongoDB connection string in the .env file. The format is identical to what Mongoose uses. [src3, src6]
# .env
DATABASE_URL="mongodb+srv://username:[email protected]/mydb?retryWrites=true&w=majority"
If you get SCRAM failure: Authentication failed, append ?authSource=admin to the connection string.
Verify: npx prisma db pull connects without errors.
Prisma samples up to 1,000 documents per collection to infer the schema. Ensure your database has representative data before running introspection. Fields with inconsistent types across documents will be mapped to Json with a warning. [src1, src3]
npx prisma db pull
This generates models in prisma/schema.prisma. Review the output — it will have no relations defined. You must add those manually.
Verify: Open prisma/schema.prisma — each collection should appear as a model block.
Introspection won't detect relations or embedded documents automatically. Add @relation attributes for references between collections, and convert embedded subdocuments to composite type blocks. Map collection names with @@map() since Mongoose auto-pluralizes. [src1, src4, src5]
// prisma/schema.prisma
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String @unique
name String?
posts Post[]
address Address? // embedded document (composite type)
v Int? @map("__v") @ignore // Mongoose version key
@@map("users") // Mongoose auto-pluralizes collection names
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
content String?
authorId String @db.ObjectId
author User @relation(fields: [authorId], references: [id])
@@map("posts")
}
// Composite type for embedded documents
type Address {
street String
city String
zip String
}
Verify: npx prisma validate returns no errors.
Generate the type-safe client and sync indexes/constraints to MongoDB. [src1]
npx prisma generate
npx prisma db push
Verify: npx prisma studio opens a GUI showing your collections and data.
Migrate one route/service at a time. Import PrismaClient alongside your Mongoose models and switch queries incrementally. [src1, src2]
// BEFORE: Mongoose
const mongoose = require('mongoose');
const User = mongoose.model('User');
app.get('/users/:id', async (req, res) => {
const user = await User.findById(req.params.id).populate('posts');
res.json(user);
});
// AFTER: Prisma
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
app.get('/users/:id', async (req, res) => {
const user = await prisma.user.findUnique({
where: { id: req.params.id },
include: { posts: true },
});
res.json(user);
});
Verify: API responses match between Mongoose and Prisma versions for the same endpoints.
Once all queries are migrated, uninstall Mongoose and remove schema files. [src1]
npm uninstall mongoose
# Delete Mongoose model files (e.g., models/User.js, models/Post.js)
# Remove mongoose.connect() from your entry point
grep -rn 'mongoose' --include='*.js' --include='*.ts' | grep -v node_modules
Verify: grep returns zero results. App runs without Mongoose loaded.
Full script: javascript-converting-a-mongoose-crud-service-to-p.js (61 lines)
// Input: Express route handlers using Mongoose for User CRUD
// Output: Same handlers using Prisma Client with full error handling
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
// CREATE
async function createUser(req, res) {
try {
const user = await prisma.user.create({
data: {
email: req.body.email,
name: req.body.name,
address: { set: { street: req.body.street, city: req.body.city, zip: req.body.zip } },
},
});
res.status(201).json(user);
} catch (err) {
if (err.code === 'P2002') res.status(409).json({ error: 'Email already exists' });
else res.status(500).json({ error: err.message });
}
}
Full script: typescript-converting-mongoose-middleware-to-prism.ts (49 lines)
// Input: Mongoose pre-save hooks and virtuals
// Output: Prisma Client extensions that replicate the same behavior
import { PrismaClient } from '@prisma/client';
// Mongoose middleware equivalent via Prisma Client extensions
const prisma = new PrismaClient().$extends({
query: {
user: {
async create({ args, query }) {
if (args.data.email) {
args.data.email = (args.data.email as string).toLowerCase().trim();
}
args.data.createdAt = args.data.createdAt ?? new Date();
args.data.updatedAt = new Date();
return query(args);
},
},
},
});
Full script: typescript-converting-mongoose-schema-with-embedde.ts (68 lines)
// Input: Mongoose schema with nested subdocuments and arrays
// Output: Equivalent Prisma schema + TypeScript query code
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Create order with embedded documents
async function createOrder(customerId: string) {
return prisma.order.create({
data: {
customerId,
items: [
{ product: 'Keyboard', quantity: 1, price: 79.99 },
{ product: 'Mouse', quantity: 2, price: 29.99 },
],
shippingAddress: { set: { street: '123 Main St', city: 'Berlin', country: 'DE' } },
status: 'pending',
},
include: { customer: true },
});
}
prisma migrate with MongoDB# ❌ BAD — prisma migrate does not support MongoDB
# This will fail with: "Error: Prisma Migrate does not support MongoDB"
npx prisma migrate dev --name add_users
prisma db push for MongoDB schema changes# ✅ GOOD — db push syncs your schema to MongoDB without migration files
npx prisma db push
_id directly in Prisma queries// ❌ BAD — Prisma maps _id to id via @map("_id")
const user = await prisma.user.findUnique({
where: { _id: '507f1f77bcf86cd799439011' }, // Error: Unknown field _id
});
id// ✅ GOOD — Use the Prisma field name, not the MongoDB field name
const user = await prisma.user.findUnique({
where: { id: '507f1f77bcf86cd799439011' },
});
// ❌ BAD — Creates a new connection pool on every request
app.get('/users', async (req, res) => {
const prisma = new PrismaClient(); // New instance per request!
const users = await prisma.user.findMany();
res.json(users);
});
// ✅ GOOD — Single instance reused across all requests
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
process.on('beforeExit', async () => {
await prisma.$disconnect();
});
app.get('/users', async (req, res) => {
const users = await prisma.user.findMany();
res.json(users);
});
// ❌ BAD — Manually creating ObjectId like in Mongoose
const { ObjectId } = require('mongodb');
const user = await prisma.user.findUnique({
where: { id: new ObjectId('507f1f77bcf86cd799439011') },
});
// ✅ GOOD — Prisma auto-converts strings to ObjectId when @db.ObjectId is declared
const user = await prisma.user.findUnique({
where: { id: '507f1f77bcf86cd799439011' },
});
// ❌ BAD — Mongoose/MongoDB query syntax doesn't work in Prisma
const users = await prisma.user.findMany({
where: { age: { $gte: 18, $lte: 65 } },
});
// ✅ GOOD — Prisma uses its own filter operators
const users = await prisma.user.findMany({
where: { age: { gte: 18, lte: 65 } },
});
prisma.$use() middleware (removed in Prisma 7)// ❌ BAD — $use() middleware is deprecated in v6 and removed in v7
prisma.$use(async (params, next) => {
params.args.data.updatedAt = new Date();
return next(params);
});
$extends)// ✅ GOOD — $extends is the stable API for query interception
const prisma = new PrismaClient().$extends({
query: {
$allModels: {
async $allOperations({ args, query }) {
if (args.data) args.data.updatedAt = new Date();
return query(args);
},
},
},
});
prisma db pull cannot detect references between collections. Fix: Manually add @relation(fields: [...], references: [...]) attributes and reference fields with @db.ObjectId. [src1]__v version key breaks Prisma schema: Mongoose adds a __v field to every document. After introspection, Prisma includes it but doesn't need it. Fix: Add @ignore to the v field: v Int? @map("__v") @ignore. [src5]User becomes users). Prisma uses the model name as-is. Fix: Add @@map("users") to your Prisma model to match the existing collection name. [src5]mongod --replSet rs0 locally or ensure your Atlas cluster is a replica set (default for Atlas). [src3]Json. [src3]findUnique() cannot filter on composite type fields, and aggregation operations do not support composite types. Fix: Use findFirst() with composite type filters instead. [src4]view blocks or refactor views to exclude embedded document fields. [src4]pre('save') or post('find') hooks. The $use() middleware API is deprecated in v6 and removed in v7. Fix: Use Prisma Client extensions ($extends) for query-level middleware, or move logic to your application service layer. [src2, src7]# Check Prisma version and MongoDB connection
npx prisma --version
npx prisma db pull --print
# Validate your Prisma schema for errors
npx prisma validate
# Open Prisma Studio to browse data visually
npx prisma studio
# Generate the Prisma Client after schema changes
npx prisma generate
# Push schema changes to MongoDB (not migrate!)
npx prisma db push
# Check for remaining Mongoose references in codebase
grep -rn 'mongoose\|Schema\|\.populate(' --include='*.js' --include='*.ts' | grep -v node_modules | wc -l
# Check MongoDB replica set status (mongosh)
# rs.status()
# Verify Prisma is on v6 (not v7) for MongoDB projects
npx prisma --version | head -1
# Expected: prisma : 6.19.x
| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| Prisma 7.x (2025) | Current (no MongoDB) | ESM-only, driver adapters mandatory, $use() removed, new generator syntax | Do NOT use for MongoDB — stay on v6.19 until MongoDB support ships |
| Prisma 6.x (2025) | LTS for MongoDB | ESM-first client, new generator syntax | Use prisma@6 + @prisma/client@6 — v6.19 is latest MongoDB-compatible release |
| Prisma 5.x (2023) | Maintenance | jsonProtocol default, removed rejectOnNotFound | Update error handling for not-found queries |
| Prisma 4.x (2022) | EOL | extendedIndexes, filterJson GA | No MongoDB-specific breaking changes |
| Prisma 3.12+ (2022) | EOL | Composite types GA | First version with stable embedded document support |
| Mongoose 8.x (2024) | Current | Dropped Node.js 14 support | Note new default behaviors when migrating from 8.x |
| Mongoose 7.x (2023) | LTS | Dropped callback support | Convert callbacks to async/await before migrating to Prisma |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| You need full TypeScript type safety for queries | You rely heavily on Mongoose plugins | Keep Mongoose or wrap plugins |
| Your project uses multiple databases | Your app is deeply invested in aggregation pipelines | Mongoose with native driver for aggregation |
| You want auto-generated types from schema | You need MongoDB-specific features (change streams, GridFS, $graphLookup) | Mongoose or native MongoDB driver |
| Your team is building a new service alongside legacy | Your MongoDB data has highly inconsistent field types | Clean data first, then consider Prisma |
| You want visual database browsing via Prisma Studio | You need Mongoose-style middleware for complex logic | Use Prisma $extends or application-layer middleware |
| You want a single ORM for both SQL and MongoDB | Your project needs full Prisma v7 features (ESM-only, driver adapters) | Wait for Prisma v7 MongoDB support |
prisma migrate dev and prisma migrate deploy are not supported for MongoDB — always use prisma db push to sync schema changes.@ignore attributes).mongod instance will fail on any operation that uses prisma.$transaction()..lean() returns plain objects; Prisma Client always returns plain objects by default (no hydration overhead).prisma.$use() middleware API is deprecated in Prisma 6 and completely removed in Prisma 7. Use $extends for all new query interception logic.