Dev.to WebDev πŸ›  Dev πŸ‘ 1 πŸ“– 4 min read

Why We Don't Need 'prisma generate' Anymore for Multi-Database Apps

If you have built production Node.js or Next.js applications over the last few years, chances are high that Prisma was your default ORM choice. And for good reason: Prisma revolutionized developer experience (DX) in th

If you have built production Node.js or Next.js applications over the last few years, chances are high that Prisma was your default ORM choice.

And for good reason: Prisma revolutionized developer experience (DX) in the TypeScript ecosystem. Being able to write nested relational queries using intuitive JavaScript objects completely eliminated the need to construct complex SQL joins by hand:


// The DX we all fell in love with

const user = await prisma.user.findMany({

  where: { role: 'ADMIN' },

  include: {

    posts: true,

    profile: true,

  },

});

However, as production architectures shifted heavily toward serverless edge runtimes, fast containerized CI/CD builds, microservices, and AI vector workflows, the hidden tax of Prisma’s core architecture started catching up to developers:

  1. prisma generate Build Steps: Every code change requires running a generation step that outputs files into disk/node_modules, leading to out-of-sync type definitions during local development or Docker builds.

  2. Monorepo & CI Friction: Managing generated clients across multiple packages or serverless Lambdas frequently leads to missing binary target errors or bloated deployment packages.

  3. Missing First-Class AI & Spatial Types: Querying vector embeddings for AI apps or geospatial map coordinates often forces you out of standard ORM methods and back into raw SQL query strings ($queryRaw).

What if we could keep Prisma’s clean, object-based API syntaxβ€”findMany, include, select, whereβ€”without needing a build-step code generator at all?

Enter Zero-Codegen Architecture with forge-orm.

The Problem with Code Generation in Modern Stacks

To understand why zero-codegen is gaining momentum, let's look at what actually happens when you run prisma generate:


   +-------------------+

   |   schema.prisma   |

   +---------+---------+

             |

             | (CLI / Code Generator Step)

             v

   +-------------------+

   | Generated .ts/.js |  ---> Written to disk / node_modules

   +---------+---------+

             |

             v

   +-------------------+

   | Application Code  |  ---> Consumes static output

   +-------------------+

While this static generation approach guarantees tight typings, it creates subtle development friction:

  • Hot Reload Lag: In fast-moving projects, tweaking your database schema means re-running generation before your IDE or HMR server picks up the new types.

  • Deployment Cold Starts: Bundling generated artifacts alongside heavy engine adapters increases bundle sizes on serverless platforms like Vercel or AWS Lambda.

  • Schema Drift in Teams: Merge conflicts in schema files often result in local environments running on stale generated client types.

How Zero-Codegen Works Under the Hood

Instead of compiling code to disk before your application runs, modern zero-codegen ORMs like forge-orm combine TypeScript Generics and native JavaScript Proxy objects at runtime.

When you call a query method:


const posts = await db.post.findMany({

  include: { author: true }

});

Instead of looking up a statically generated client file, the runtime proxy intercepts the property access (db.post), inspects your query parameters, dynamically formats the database query, and executes it directly through native JavaScript database drivers.

Benefits of Zero-Codegen:

  • Zero Build Step: No postinstall script or npx prisma generate command in your Dockerfile or CI/CD pipelines.

  • Instant Type Inference: Types update automatically in your IDE the second you update your model interfacesβ€”no generation command needed.

  • Edge & WASM Ready: Runs seamlessly across Node.js, Bun, Deno, Vercel Edge, and even in-browser WASM runtimes without platform-specific binaries.

Comparing the Developer Experience

Let's look at how query ergonomics compare across popular TypeScript ORMs when fetching a post with its author and comments:

Option A: SQL Query Builders (e.g., Kysely)

SQL query builders give you full SQL control, but querying nested relations requires manual subqueries:


const posts = await db

  .selectFrom('post')

  .selectAll('post')

  .select((eb) => [

    jsonArrayFrom(

      eb.selectFrom('comment')

        .selectAll('comment')

        .whereRef('comment.postId', '=', 'post.id')

    ).as('comments')

  ])

  .execute();

Option B: Zero-Codegen Prisma Syntax (forge-orm)

forge-orm preserves the nested object model that developers love, without requiring a CLI generation step or .prisma file:


import { Forge } from 'forge-orm';



const db = new Forge({ /* connection config */ });



const posts = await db.post.findMany({

  where: { published: true },

  include: {

    author: true,

    comments: true,

  },

});

First-Class Support for Modern Data: Vectors & Maps

One of the biggest limitations of legacy ORM engines is handling modern data types like AI Vector Embeddings (for similarity searches and RAG pipelines) and Geospatial Map Coordinates.

In standard setups, performing a vector similarity search usually means dropping into raw SQL:


// Legacy approach: Dropping out of type safety for vector search

const items = await prisma.$queryRaw`

  SELECT * FROM "Item" 

  ORDER BY embedding <-> ${vector}::vector 

  LIMIT 5;

`;

With native vector and spatial support built directly into the ORM's model syntax, you can query embeddings and geographical data using standard object methods:


// Native Vector Search in forge-orm

const similarDocs = await db.document.findMany({

  where: {

    embedding: {

      near: [0.12, 0.84, -0.31, 0.45],

      distance: 0.8,

    },

  },

  select: {

    title: true,

    content: true,

  },

});



// Native Spatial / Map Location Query

const nearbyStores = await db.store.findMany({

  where: {

    location: {

      withinRadius: {

        latitude: 6.5244,

        longitude: 3.3792,

        radiusInKm: 10,

      },

    },

  },

});

Feature Comparison Matrix

| Feature | Legacy Prisma | Drizzle ORM | Forge-ORM |

| :--- | :--- | :--- | :--- |

| API Mental Model | Nested Object Trees (include, select) | SQL-like Query Builder | Nested Object Trees (include, select) |

| Code Generation | Required (prisma generate) | Optional / Schema-driven | Zero Codegen |

| Build Step Required | Yes | Minimal | None (Pure TS Import) |

| Native Vector Support | Raw SQL / Extensions | Custom SQL column functions | First-Class Method Support |

| Native Map/Spatial Types | Raw SQL | Manual SQL casting | First-Class Method Support |

| Runtime Portability | WASM / Client Adapters | Lightweight JS Drivers | Pure JS/TS (Node, Edge, Mobile, WASM) |

Conclusion

Code generation was a necessary stepping stone in the early days of TypeScript ORMs to guarantee strict typings. But as TypeScript's type engine has evolved alongside runtime proxies and native database drivers, we no longer need to sacrifice build speed and deployment simplicity just to get great DX.

If you are tired of debugging prisma generate pipeline failures, wrestling with native binary targets in Docker, or writing raw SQL for vector embeddings, give a zero-codegen ORM a try in your next project.

  • Check out forge-orm on npm to explore the zero-codegen approach.

  • What are your thoughts on codegen vs. zero-codegen architectures in production apps? Drop a comment below!

πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.