How to Migrate from Mongoose to Prisma

Type: Software Reference Confidence: 0.88 Sources: 8 Verified: 2026-02-23 Freshness: quarterly

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

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 (2025)Current (no MongoDB)ESM-only, driver adapters mandatory, $use() removed, new generator syntaxDo NOT use for MongoDB — stay on v6.19 until MongoDB support ships
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 full Prisma v7 features (ESM-only, driver adapters)Wait for Prisma v7 MongoDB support

Important Caveats

Related Units