DEV Community

Cover image for I built a 13-database ORM for Node.js because Prisma doesn't support Oracle
MADANI
MADANI

Posted on • Edited on • Originally published at mostajs.dev

I built a 13-database ORM for Node.js because Prisma doesn't support Oracle

🇫🇷 Pourquoi j'ai créé @mostajs/orm — 13 bases de données, une seule API, zéro génération de code.

By Dr Hamid MADANI — published on dev.to / Medium / Hashnode


TL;DR

@mostajs/orm is a Hibernate/JPA-inspired ORM for Node.js & TypeScript that supports 13 databases through a single API (PostgreSQL, MySQL, MariaDB, SQLite, MongoDB, Oracle, MSSQL, CockroachDB, DB2, SAP HANA, HSQLDB, Spanner, Sybase). No codegen step. No .prisma DSL. Just plain TypeScript objects.

Test coverage, stated honestly: 8 of the 13 engines are validated end-to-end — PostgreSQL, MySQL, MariaDB, SQLite, MongoDB, SQL Server, Oracle, HSQLDB (including the WASM runtimes sqljs and pglite). The other 5 — CockroachDB, DB2, SAP HANA, Spanner, Sybase — are implemented but not yet validated end-to-end (hard-to-provision enterprise/cloud engines). No "coming soon" in disguise: the dialects exist; their E2E test status is disclosed.

If you've ever wished Prisma had Hibernate's depth, or that Drizzle worked with MongoDB, or that TypeORM was actually maintained — this is for you. And with its WASM dialects (sqljs / pglite), the same code runs in the browser and in AI dev tools (Bolt.new, StackBlitz, CodeSandbox) with no native binary — so the starters below open and run in one click.

npm i @mostajs/orm
Enter fullscreen mode Exit fullscreen mode

The problem

I've spent 15+ years writing data layers — Java/Hibernate, Spring Data, then years in the Node ecosystem with TypeORM, Sequelize, Prisma, Drizzle, Mongoose. Each one is great at something, and frustrating at something else.

After shipping ~30 production apps, three pain points kept coming back:

  1. Lock-in by ORM choice. Pick Prisma → no MongoDB. Pick Mongoose → no SQL. Pick Drizzle → no document store. Each migration project means rewriting the data layer.
  2. Codegen friction. prisma generate after every schema change. Stale types in CI. Editor lag. Lost minutes that compound into hours.
  3. Loss of Hibernate concepts. CascadeType, FetchType, hbm2ddl.auto, lazy loading proxies — these aren't bloat, they're battle-tested patterns. Node ORMs mostly throw them away.

So I built one that fixes the three.


Feature 1 — One API, 13 databases

The killer feature. You write your schema once. You change one config line to switch backend.

// SQLite for dev
{ dialect: 'sqlite', uri: './app.db' }

// PostgreSQL for staging
{ dialect: 'postgres', uri: 'postgres://...' }

// MongoDB for that one client who insists
{ dialect: 'mongo', uri: 'mongodb://...' }

// Oracle for the enterprise contract
{ dialect: 'oracle', uri: '...' }
Enter fullscreen mode Exit fullscreen mode

The four engines above (SQLite, PostgreSQL, MongoDB, Oracle) are all tested end-to-end — see the TL;DR for the full 8-tested / 5-pending breakdown.

The repository API stays identical:

const users = new BaseRepository<User>(UserSchema, dialect);

await users.create({ email: 'a@b.c', name: 'Alice' });
await users.findOne({ email: 'a@b.c' });
await users.findAll(
  { age: { $gte: 18 }, $or: [{ role: 'admin' }, { verified: true }] },
  { sort: { createdAt: -1 }, limit: 20 },
);
Enter fullscreen mode Exit fullscreen mode

That $gte/$or syntax works on PostgreSQL, MySQL, MongoDB, Oracle — all of them. The dialect adapts.

Why this matters for AI dev tools. When Bolt.new or Cursor generates code, it doesn't have to "know" your DB. One mental model, every backend.


Feature 2 — Pure TypeScript schemas, zero codegen

No prisma generate. No .prisma file. No DSL.

import type { EntitySchema } from '@mostajs/orm';

export const PostSchema: EntitySchema = {
  name: 'Post',
  collection: 'posts',
  fields: {
    title:     { type: 'string', required: true, trim: true },
    slug:      { type: 'string', required: true, unique: true, sparse: true },
    content:   { type: 'text', required: true },
    published: { type: 'boolean', default: false },
  },
  relations: {
    author:   { target: 'User',    type: 'many-to-one', required: true, onDelete: 'cascade' },
    comments: { target: 'Comment', type: 'one-to-many', mappedBy: 'post',
                cascade: ['persist', 'remove'], orphanRemoval: true },
  },
  indexes: [{ fields: ['slug'], type: 'unique' }],
  timestamps: true,
  softDelete: true,
};
Enter fullscreen mode Exit fullscreen mode

That's it. Type-safe immediately. No build step. Edit the schema, save, hot-reload picks it up.


Feature 3 — Hibernate/JPA concepts, finally in Node

If you've ever used Hibernate, this will feel like home:

JPA / Hibernate @mostajs/orm
@OneToMany, @ManyToOne, @ManyToMany relations: { type: 'one-to-many', ... }
CascadeType.PERSIST/MERGE/REMOVE/ALL cascade: ['persist', 'merge', 'remove']
FetchType.LAZY / EAGER `fetch: 'lazy' \
{% raw %}@OnDelete(CASCADE) onDelete: 'cascade'
orphanRemoval = true orphanRemoval: true
hbm2ddl.auto = validate/update/create/create-drop `schemaStrategy: 'validate' \
{% raw %}persistence.xml ConnectionConfig

If you're a Java dev moving to Node, you'll be productive in 10 minutes.


Feature 4 — MongoDB-style filters, on every database

const recent = await posts.findAll({
  published: true,
  createdAt: { $gte: new Date(Date.now() - 7 * 86400000) },
  $or: [
    { tags: { $in: ['typescript', 'orm'] } },
    { author: { $in: featuredAuthors } },
  ],
  title: { $regex: /next\.?js/i },
});
Enter fullscreen mode Exit fullscreen mode

Works on PostgreSQL. Works on SQLite. Works on MongoDB. Works on Oracle. The dialect translates $gte>=, $inIN (...), $regexLIKE / ~ / $regex, etc.

You don't learn a new query language per DB. You write MongoDB filters, and they run everywhere.


Feature 5 — Built-in schema validator (24 rules)

This is the one nobody talks about, and it's saved me dozens of hours.

@mostajs/orm ships with a CLI validator that lints your schemas:

npx mostajs-orm-validator --src ./src
Enter fullscreen mode Exit fullscreen mode

24 rules out of the box, including:

  • R003 — soft-delete fields detected but no native flag → suggest softDelete: true
  • R003B — unique non-sparse index on soft-deleted entity → blocks reinsertion after soft delete (auto-fixable)
  • R009findOne lookup without matching index → suggests adding one
  • R013 — relation without cascade where required
  • R013Bfetch: 'eager' without onDelete → orphans populated silently (auto-fixable)
  • R019repo.findById(entity.relation) where relation is lazy → wraps with extractRelId() automatically
  • R021 — direct comparison entity.relation === value under eager fetch → always false bug (auto-fixable)

Try getting that out of Prisma. You can't.


Feature 6 — JDBC bridge for legacy databases

Need to connect to DB2, SAP HANA, Sybase, or HSQLDB from Node? They have no native Node drivers worthy of production.

@mostajs/orm/bridge exposes a JDBC bridge that runs a Java sidecar process. Same API, no compromise.

import { BridgeManager, saveJarFile } from '@mostajs/orm/bridge';

await saveJarFile('./drivers/db2.jar');
const dialect = await createConnection({
  dialect: 'db2',
  uri: 'jdbc:db2://host:50000/MYDB',
});
Enter fullscreen mode Exit fullscreen mode

For the enterprise/banking/healthcare crowd, this alone is reason enough to switch.


Feature 7 — Multi-connection, multi-tenant ready

getDialect() is a singleton for the simple case. For multi-tenant SaaS where each tenant has its own DB:

import { createIsolatedDialect } from '@mostajs/orm';

const tenantA = await createIsolatedDialect({ dialect: 'postgres', uri: tenantA_URI }, ALL_SCHEMAS);
const tenantB = await createIsolatedDialect({ dialect: 'postgres', uri: tenantB_URI }, ALL_SCHEMAS);

// Concurrent, isolated, type-safe
Enter fullscreen mode Exit fullscreen mode

Feature 8 — Runs in the browser & WebContainers (WASM)

better-sqlite3 is a native addon — it doesn't compile in Bolt.new / StackBlitz / CodeSandbox or on the edge. So @mostajs/orm ships WASM dialects with the same API:

  • sqljs — SQLite compiled to WebAssembly. Zero native binary, boots in the browser, WebContainers and edge runtimes.
  • pglite — PostgreSQL in WASM, with native IndexedDB persistence (uri: 'idb://my-db').
// same code, no native binary — boots in Bolt.new on the first try
const dialect = await createConnection(
  { dialect: 'sqljs', uri: ':memory:', schemaStrategy: 'update' },
  ALL_SCHEMAS,
);
Enter fullscreen mode Exit fullscreen mode

This is why the starters below open and run in AI dev tools instantly — the thing that makes an ORM discoverable by Bolt, Lovable, v0 and Cursor. Switch one config line (dialect: 'postgres') for production; the code is identical.


Quick comparison

@mostajs/orm Prisma Drizzle TypeORM Mongoose
SQL databases 12 6 6 8 0
MongoDB ❌ (preview)
Codegen step
Schema in TS ✅ (decorators)
MongoDB-like filters
Hibernate semantics partial partial
JDBC bridge
Built-in linter ✅ (24 rules)
Edge runtime ✅ (WASM) ⚠️ (Accelerate $)
Browser / WebContainer (Bolt, StackBlitz) ✅ (WASM)
Active maintenance ⚠️
License AGPL-3.0 / commercial Apache 2.0 Apache 2.0 MIT MIT

Getting started

npm i @mostajs/orm
Enter fullscreen mode Exit fullscreen mode
import { createConnection, BaseRepository, type EntitySchema } from '@mostajs/orm';

const UserSchema: EntitySchema = {
  name: 'User',
  collection: 'users',
  fields: {
    email: { type: 'string', required: true, unique: true },
    name:  { type: 'string' },
  },
  relations: {},
  indexes: [],
  timestamps: true,
};

const dialect = await createConnection(
  { dialect: 'sqlite', uri: './app.db', schemaStrategy: 'update' },
  [UserSchema],
);

const users = new BaseRepository<{ id: string; email: string; name?: string }>(UserSchema, dialect);
await users.create({ email: 'a@b.c', name: 'Alice' });
Enter fullscreen mode Exit fullscreen mode

That's a working app. No codegen. No migrations file. The schemaStrategy: 'update' reconciles the DB schema on boot (use 'validate' in production).


Open source & commercial use

@mostajs/orm is AGPL-3.0. If your project is open source under a compatible license, you're free to use it.

For closed-source / SaaS / commercial products, a commercial license is available. Contact mostajs.dev — pricing scales with company size, no per-seat fees, no per-query fees.

This dual-license model funds active development, the same way Sentry, MongoDB, BullMQ Pro, and Cal.com do.


Starters — open in your browser (no install)

Six public starters, each boots with no native binary via the sqljs (SQLite WASM) dialect. Tested E2E (June 2026): StackBlitz 6/6 ✅ · CodeSandbox 6/6 ✅ · Bolt.new (the server starters run as-is; the Next ones run in prod — Bolt's runtime doesn't run Next dev).

Starter What it shows Open in
nextjs / express / fastify / hono -mostajs-orm-starter Blog (Users · Posts · Comments) — relations, soft-delete, seed StackBlitz · Bolt · CodeSandbox
mostajs-saas-starter SaaS MVP — landing + auth (@mostajs/auth-lite) + CRUD dashboard StackBlitz · Bolt · CodeSandbox
mostajs-survey-starter Survey + admin dashboard (bar charts) StackBlitz · Bolt · CodeSandbox

Samples & AI tooling

  • 📚 Runnable samplesnpx @mostajs/orm-samples scaffold <name> drops one runnable snippet per feature (relations, soft-delete, transactions, migrations…).
  • 🤖 MCP server@mostajs/orm-mcp is listed in the official MCP Registry (io.github.apolocine/orm-mcp). In Claude / Cursor / Cline, ask "generate a mostajs/orm schema" and the AI calls the tool: generate EntitySchema, lint (24 rules), produce SQL migrations. A public instance is hosted at https://orm-mcp.amia.fr/mcp (Streamable HTTP).

Add it to your AI tool — hosted (no install), or local via npx:

  // Hosted (clients that speak HTTP)
  { "mcpServers": { "mostajs-orm": { "url": "https://orm-mcp.amia.fr/mcp" } } }

  // stdio-only clients → bridge the hosted server
  { "mcpServers": { "mostajs-orm": {
      "command": "npx", "args": ["-y", "mcp-remote", "https://orm-mcp.amia.fr/mcp"] } } }

  // Local stdio (the AI tool spawns the process)
  { "mcpServers": { "mostajs-orm": {
      "command": "npx", "args": ["-y", "@mostajs/orm-mcp"] } } }
Enter fullscreen mode Exit fullscreen mode

In practice — once connected, you just talk to your AI tool. No SDK, no docs to read:

  You:  Model a blog: a User has many Posts, each Post has many Comments. Soft-delete everywhere.
  AI:   → calls mostajs_generate_schema
        ✓ 3 EntitySchemas (User, Post, Comment) with relations + softDelete
  AI:   → calls mostajs_validate
        ✓ 24/24 rules pass — no missing inverse relation, no orphan FK
  AI:   → calls mostajs_create_migration
        ✓ SQL migration emitted (postgres) — switch one line for the other 12 dialects
Enter fullscreen mode Exit fullscreen mode

Don't take my word for it — run the whole chain. Sample 18-mcp-to-running-app does this end-to-end: the MCP generates the schemas for an e-commerce model (users/products/orders), @mostajs/orm applies them, and the app runs on sqljs (SQLite WASM, zero native binary) — with an HTML proof report next to the real insert/select output.

  npx @mostajs/orm-samples scaffold 18-mcp-to-running-app ~/demo
  cd ~/demo && bash scripts/02-report.sh        # → report.html
Enter fullscreen mode Exit fullscreen mode

Connecting the hosted instance from a stdio-only client looks like this — and that's the whole setup:

  $ npx mcp-remote https://orm-mcp.amia.fr/mcp
  [mcp-remote] Connecting to remote server: https://orm-mcp.amia.fr/mcp
  [mcp-remote] Connected using StreamableHTTPClientTransport
  [mcp-remote] Proxy established between local STDIO and remote server
Enter fullscreen mode Exit fullscreen mode

The ecosystem around it

@mostajs/orm is the core — the same schema model powers a few focused, already-published tools. Each gets its own deep-dive post soon; the one-liners:

  • 🚚 @mostajs/orm-copy-data — cross-dialect data copy/dump: one source (DB / CSV / JSON / SQL) → N destinations. Backup, migration, seeding. CLI + API, cron-ready.
  • 🔁 @mostajs/replicator — replication manager: master/slave CQRS, cross-dialect CDC, read routing, failover.
  • 📊 @mostajs/replica-monitor — live web dashboard for the replicator (reads the replication tree directly — no DB connection needed).
  • ⚙️ @mostajs/orm-cli — universal CLI: mostajs bootstrap migrates a Prisma project (codemod + deps + schema convert + DDL) to 13 databases with zero code change.

Same EntitySchema, same 13-database reach — these extend the ORM into backup, replication and migration without a second mental model.

Try it now


If you like this, a ⭐ on GitHub helps a lot — it's the strongest signal for AI dev tools (Bolt, Cursor, v0, Lovable) to start surfacing the library.

Top comments (0)