How to Migrate from Mongoose to Prisma

How do I migrate from Mongoose to Prisma?

TL;DR

Constraints

Quick Reference

Mongoose PatternPrisma EquivalentExample
new Schema({ name: String })model User { name String }Define in schema.prisma instead of JS
mongoose.model('User', schema)prisma.userAuto-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 AddressUse type keyword for embedded docs

Decision Tree

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

Decision Logic

Agent-facing if/then rules for recommending a Mongoose-to-Prisma path. Apply top-down; the first matching rule wins.

If your project uses MongoDB

--> Pin Prisma to v6.19 (the latest v6). Prisma v7 (GA January 2026) shipped without MongoDB; MongoDB support is coming via Prisma Next, not v7. Do not run npm install prisma@latest. [src9, src7]

If you are already on Prisma v7 and need MongoDB

--> Downgrade to prisma@6 + @prisma/client@6. v7's Rust-free engine and ESM-only client do not yet carry the MongoDB connector. Track the connector/mongodb label and watch for an RC that explicitly names MongoDB. [src9, src10]

If your Mongoose schema uses embedded subdocuments

--> Convert each subdocument to a Prisma composite type block (MongoDB connector only) — not a separate model. Note findUnique() cannot filter composite fields and aggregation does not support them; use findFirst() instead. [src4]

If your Mongoose schema uses discriminators (polymorphic models)

--> Prisma has no discriminator equivalent. Flatten the variants into separate models, or use a single model with a type enum field plus nullable variant fields. Decide before introspection. [src1, src5]

If your code relies on Mongoose middleware (pre/post hooks)

--> Reimplement with Prisma Client extensions ($extends) at the query level, or move the logic into your service layer. Do not reach for $use() — it is removed in Prisma 7 and deprecated in v6. [src2, src7]

If you need SQL-style migration history (versioned, reviewable)

--> You cannot get it on MongoDB: prisma migrate dev/deploy are unsupported for the MongoDB connector. Use prisma db push and version your schema.prisma in git as the source of truth. [src3]

If your MongoDB runs as a standalone mongod (no replica set)

--> Enable a replica set before relying on prisma.$transaction() — standalone instances fail every transactional call. Atlas clusters are replica sets by default; locally use mongod --replSet rs0. [src3]

Step-by-Step Guide

1. Install Prisma alongside Mongoose

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".

2. Configure the database connection

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.

3. Introspect the existing MongoDB database

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.

4. Add relations and fix the schema

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.

5. Generate the Prisma Client and push schema

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.

6. Replace Mongoose queries with Prisma Client

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.

7. Remove Mongoose and clean up

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.

Code Examples

JavaScript: Converting a Mongoose CRUD service to Prisma

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 });
  }
}

TypeScript: Converting Mongoose middleware to Prisma Client extensions

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);
      },
    },
  },
});

TypeScript: Converting Mongoose schema with embedded documents to Prisma

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 },
  });
}

Anti-Patterns

Wrong: Using 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

Correct: Use prisma db push for MongoDB schema changes

# ✅ GOOD — db push syncs your schema to MongoDB without migration files
npx prisma db push

Wrong: Using _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
});

Correct: Use the mapped field name id

// ✅ GOOD — Use the Prisma field name, not the MongoDB field name
const user = await prisma.user.findUnique({
  where: { id: '507f1f77bcf86cd799439011' },
});

Wrong: Instantiating PrismaClient per request

// ❌ 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);
});

Correct: Use a singleton PrismaClient instance

// ✅ 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);
});

Wrong: Manually converting ObjectId strings

// ❌ BAD — Manually creating ObjectId like in Mongoose
const { ObjectId } = require('mongodb');
const user = await prisma.user.findUnique({
  where: { id: new ObjectId('507f1f77bcf86cd799439011') },
});

Correct: Pass plain strings — Prisma handles ObjectId conversion

// ✅ GOOD — Prisma auto-converts strings to ObjectId when @db.ObjectId is declared
const user = await prisma.user.findUnique({
  where: { id: '507f1f77bcf86cd799439011' },
});

Wrong: Using Mongoose-style $operators in Prisma filters

// ❌ BAD — Mongoose/MongoDB query syntax doesn't work in Prisma
const users = await prisma.user.findMany({
  where: { age: { $gte: 18, $lte: 65 } },
});

Correct: Use Prisma's filter syntax without dollar prefix

// ✅ GOOD — Prisma uses its own filter operators
const users = await prisma.user.findMany({
  where: { age: { gte: 18, lte: 65 } },
});

Wrong: Using 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);
});

Correct: Use Prisma Client extensions ($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);
      },
    },
  },
});

Common Pitfalls

Diagnostic Commands

# 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 History & Compatibility

VersionStatusBreaking ChangesMigration Notes
Prisma 7.x (GA 2026-01)Current (no MongoDB)Rust-free TypeScript engine (90% smaller bundle, ~3x faster queries), ESM-only, generated client emitted to project source instead of node_modules, $use() removed, prisma-client replaces prisma-client-js generatorDo NOT use for MongoDB — MongoDB ships via Prisma Next, not v7; stay on v6.19
Prisma 6.x (2025)LTS for MongoDBESM-first client, new generator syntaxUse prisma@6 + @prisma/client@6 — v6.19 is latest MongoDB-compatible release
Prisma 5.x (2023)MaintenancejsonProtocol default, removed rejectOnNotFoundUpdate error handling for not-found queries
Prisma 4.x (2022)EOLextendedIndexes, filterJson GANo MongoDB-specific breaking changes
Prisma 3.12+ (2022)EOLComposite types GAFirst version with stable embedded document support
Mongoose 8.x (2024)CurrentDropped Node.js 14 supportNote new default behaviors when migrating from 8.x
Mongoose 7.x (2023)LTSDropped callback supportConvert callbacks to async/await before migrating to Prisma

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
You need full TypeScript type safety for queriesYou rely heavily on Mongoose pluginsKeep Mongoose or wrap plugins
Your project uses multiple databasesYour app is deeply invested in aggregation pipelinesMongoose with native driver for aggregation
You want auto-generated types from schemaYou need MongoDB-specific features (change streams, GridFS, $graphLookup)Mongoose or native MongoDB driver
Your team is building a new service alongside legacyYour MongoDB data has highly inconsistent field typesClean data first, then consider Prisma
You want visual database browsing via Prisma StudioYou need Mongoose-style middleware for complex logicUse Prisma $extends or application-layer middleware
You want a single ORM for both SQL and MongoDBYour project needs Prisma v7's Rust-free engine on MongoDB todayStay on v6.19 and wait for MongoDB support in Prisma Next

Important Caveats