🇫🇷 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
sqljsandpglite). 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
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:
- 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.
-
Codegen friction.
prisma generateafter every schema change. Stale types in CI. Editor lag. Lost minutes that compound into hours. -
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: '...' }
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 },
);
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,
};
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 },
});
Works on PostgreSQL. Works on SQLite. Works on MongoDB. Works on Oracle. The dialect translates $gte → >=, $in → IN (...), $regex → LIKE / ~ / $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
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)
-
R009 —
findOnelookup without matching index → suggests adding one -
R013 — relation without
cascadewhere required -
R013B —
fetch: 'eager'withoutonDelete→ orphans populated silently (auto-fixable) -
R019 —
repo.findById(entity.relation)where relation is lazy → wraps withextractRelId()automatically -
R021 — direct comparison
entity.relation === valueunder 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',
});
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
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,
);
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
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' });
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 samples —
npx @mostajs/orm-samples scaffold <name>drops one runnable snippet per feature (relations, soft-delete, transactions, migrations…). - 🤖 MCP server —
@mostajs/orm-mcpis 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: generateEntitySchema, lint (24 rules), produce SQL migrations. A public instance is hosted athttps://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"] } } }
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
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
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
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 bootstrapmigrates 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
- 📦 npm — npmjs.com/package/@mostajs/orm
- 🐙 GitHub — github.com/apolocine/mosta-orm
- 🌐 Website — mostajs.dev
- 🤖 LLM card — llms.txt
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)