Startups·NewsTide Editorial·Jun 28, 2026·8 min read·🇪🇸 ES

When Prisma Became the Only Viable Path for Wally to Migrate from MongoDB to Postgres Without Breaking Production

When Wally's team first attempted to migrate their database from MongoDB to Postgres, they lost 72 hours of development time and nearly broke two weeks of billing. They had underestimated the complexity of moving from a non-relational schema to a strict SQL model and lacked an ORM to handle the heavy lifting. However, once they tried Prisma, the story changed: the entire migration took four days, with no service downtime, and a reliable rollback system.

geometric shape digital wallpaper
Photo: fabio on Unsplash

This is not an isolated case. Prisma has evolved from being "just another trendy ORM" to becoming the centerpiece of migration architectures in startups that need to switch their data stack. Let's break down how they achieved this, what technical errors they avoided, and why this tool is outpacing TypeORM and Sequelize in 2026.

The Real Context: Why Wally Needed to Abandon MongoDB

Wally is a Mexican fintech company that automates cash management for small businesses. They started in 2023 with MongoDB because it was quick for prototyping, didn't require rigid schemas, and the whole team came from startups that used it. It worked well until they reached 150,000 active users.

The problem arose when they needed to implement complex financial transactions. Sure, MongoDB has supported ACID transactions since version 4.0, but the reality is that its implementation is slow and requires properly configured replicas. Moreover, it was never the engine's main use case. Every time they ran a nightly bank reconciliation, the cluster would get bogged down. The ad-hoc JOIN queries using $lookup were a performance disaster.

The decision was clear: migrate to Postgres. However, doing so without the right tools meant rewriting the entire data access layer. They would have to manage migrations manually and pray that no edge cases would break production. This is where Prisma came into play.

Why Prisma and Not TypeORM or Writing SQL Directly

a blue abstract background with lines and dots
Photo: Conny Schneider on Unsplash

Wally's CTO's first proposal was to use TypeORM, the "obvious" choice for TypeScript teams. After two weeks of testing, they ruled it out. TypeORM has a mature ecosystem, but its migration system is fragile. This becomes a problem when making large structural changes. Generating automatic migrations from entity decorators works well for incremental changes, but when migrating from a completely different data model, you end up writing SQL by hand anyway.

The alternative of writing pure SQL with a query builder like Knex.js also fell by the wayside. Wally's team had six developers, three of whom were junior. Managing complex migrations, relationships between tables, and schema validations without a solid ORM would invite production problems every couple of weeks.

Prisma offered something different: a declarative flow based on the schema.prisma file, reliable automatic migrations, and a generated typed client that eliminated 90% of runtime errors. But most importantly, it could serve as a bridge between databases.

The Trick of Dual Introspection

Prisma has a little-documented but powerful command: prisma db pull. This command connects to an existing database and automatically generates a Prisma schema based on the current structure. Wally used this to create an intermediate representation of their MongoDB schema before jumping to Postgres.

The workflow was:

  1. Connect Prisma to MongoDB using the mongodb connector (supported since Prisma 3.12).
  2. Run prisma db pull to generate a base schema.
  3. Manually refactor that schema to fit an optimal relational model.
  4. Apply that refined schema to a clean Postgres instance.
  5. Write data migration scripts using Prisma Client as an abstraction.

This methodology allowed them to have total control over the data transformation without directly touching SQL. Additionally, they had the assurance that the Prisma client validated types at every step.

Step-by-Step Migration Architecture

Now, let’s get technical. Here’s how the actual implementation went down.

Step 1: Dual Simultaneous Connection

Wally configured Prisma to work with two datasources at the same time. This isn’t in the official documentation because Prisma officially supports only one datasource per schema, but there’s a workaround: using multiple schema files.

They created two files:

schema-mongo.prisma:

datasource db {
  provider = "mongodb"
  url      = env("MONGO_URL")
}

model Transaction {
  id        String   @id @default(auto()) @map("_id") @db.ObjectId
  userId    String
  amount    Float
  createdAt DateTime @default(now())
}

schema-postgres.prisma:

datasource db {
  provider = "postgresql"
  url      = env("POSTGRES_URL")
}

model Transaction {
  id        Int      @id @default(autoincrement())
  userId    Int
  user      User     @relation(fields: [userId], references: [id])
  amount    Decimal  @db.Decimal(10, 2)
  createdAt DateTime @default(now())
}

They generated two distinct Prisma clients using the --schema flag and imported them with different aliases:

import { PrismaClient as MongoClient } from './generated/mongo'
import { PrismaClient as PostgresClient } from './generated/postgres'

Step 2: Batch Migration Script

The team developed a Node.js script that ran during low traffic hours (3 AM - 6 AM CDMX time). The logic was simple but effective:

const mongoClient = new MongoClient()
const pgClient = new PostgresClient()

async function migrateTransactions(batchSize = 1000) {
  const totalCount = await mongoClient.transaction.count()
  let migrated = 0

  while (migrated < totalCount) {
    const batch = await mongoClient.transaction.findMany({
      skip: migrated,
      take: batchSize
    })

    const transformed = batch.map(tx => ({
      userId: parseInt(tx.userId), // Convert string to int
      amount: new Decimal(tx.amount),
      createdAt: tx.createdAt
    }))

    await pgClient.transaction.createMany({
      data: transformed,
      skipDuplicates: true
    })

    migrated += batch.length
    console.log(`Migrated ${migrated}/${totalCount}`)
  }
}

What surprised me the most is that the key here is skipDuplicates: true, allowing them to resume the migration if it failed midway. This was vital when a network failure interrupted the migration at batch 340.

Step 3: Dual Write Period

For two weeks, Wally ran in hybrid mode: every new transaction was written simultaneously to both MongoDB and Postgres. They used a "dual write with primary read" pattern:

async function createTransaction(data: TransactionInput) {
  // Primary write to Postgres
  const pgTx = await pgClient.transaction.create({ data })

  // Secondary write to Mongo (non-blocking)
  mongoClient.transaction.create({
    data: {
      id: pgTx.id.toString(),
      ...data
    }
  }).catch(err => {
    logger.error('Mongo write failed', err)
    // Does not block the request
  })

  return pgTx
}

Reads continued to come from Postgres. MongoDB acted as a backup in case they needed to roll back.

Step 4: Automated Cross-Validation

Every night, a cron job compared records between both databases for inconsistencies:

async function validateMigration() {
  const pgCount = await pgClient.transaction.count()
  const mongoCount = await mongoClient.transaction.count()

  if (Math.abs(pgCount - mongoCount) > 100) {
    await sendAlert('Desynchronization detected')
  }

  // Random sample validation
  const sample = await pgClient.transaction.findMany({
    take: 1000,
    orderBy: { id: 'desc' }
  })

  for (const tx of sample) {
    const mongoTx = await mongoClient.transaction.findUnique({
      where: { id: tx.id.toString() }
    })

    if (!mongoTx || mongoTx.amount !== tx.amount.toNumber()) {
      await sendAlert(`Mismatch in tx ${tx.id}`)
    }
  }
}

This system detected 43 transactions with discrepancies, all caused by a bug in decimal rounding during the initial transformation.

What Prisma Solved (and What It Didn’t)

Prisma isn’t magic. There were things that Wally's team had to resolve manually:

What Prisma Did Well:

  • Automatic TypeScript type generation eliminated schema mismatch errors.
  • The prisma migrate system was 100% reliable for incremental changes in Postgres.
  • The introspection system (db pull) sped up the design phase of the new schema.
  • Prisma Studio facilitated manual data inspection during validation.

What Required Extra Work:

  • Transforming the ObjectId (24-character string) to sequential integers needed a persistent conversion map.
  • The many-to-many relationships that were embedded arrays in Mongo required explicit pivot tables.
  • The performance of the Prisma client with complex queries (joins of 4+ tables) was about 15% lower than pure SQL.
  • Handling optional/nullable fields between schemas required extensive manual validation.

The Result: Four Days vs. Four Months

Let’s compare with the previous failed attempt without Prisma:

Attempt 1 (without Prisma, just SQL scripts):

  • 72 hours of development.
  • 14 production bugs.
  • Full rollback necessary.
  • Estimated loss: ~$18,000 USD in wasted development hours.

Attempt 2 (with Prisma):

  • 4 days of active migration.
  • 2 weeks of dual writing.
  • 3 minor bugs detected before turning off Mongo.
  • Total cost: ~$6,000 USD in development hours.

Most importantly: zero downtime. Wally's users never noticed the migration.

The Landscape in 2026: Why Other Tools Are Falling Behind

Prisma isn't the only migration tool on the market, but it's gaining ground rapidly. Drizzle ORM appeared in 2024 as a lighter alternative, but its migration system is still manual and error-prone. TypeORM remains popular in legacy projects, but its development has stagnated.

What sets Prisma apart is the complete ecosystem: Prisma Migrate, Prisma Studio, Prisma Accelerate (query caching), and now Prisma Pulse (real-time change data capture). It’s a full data management stack, not just an ORM.

For growth-stage startups needing to switch technologies without breaking everything, Prisma is becoming the default choice. Cases like Wally’s demonstrate why: the difference between a controlled migration and a production disaster can lie in the tools you choose.

Is your startup considering a database change? What’s holding you back?

Editorial note: This article was generated with AI assistance and reviewed by the NewsTide editorial team to ensure accuracy and relevance. Read our editorial policy.

More on Startups

Linear Stopped Being a Task Manager the Day it Automated Replicate's Complete RoadmapSupabase Becomes the Invisible Backend for Plata: How a Latin American Fintech Scales with Postgres and Avoids Firebase HellImplementing a Talent Retention System in AI: A Technical Guide for Startups Using AirtableThe Complete Architecture for Scaling AI Teams: Notion as a Talent CRM and GCP as Operational InfrastructureYour AI startup is going to lose three key engineers this year: here's how to protect your model before it happensNotion as the Nervous System of Your Startup: The Complete Architecture for Managing Talent in AI TeamsWhen Your AI Model Needs to Reinvent Itself: A Complete Architecture for Continuous Self-Optimization Without HumansBuilding a Chatbot with Anthropic: Step-by-Step Guide to Integrate AI into Your Startup Using Its API
← Back to homeView all Startups