Orange ORM: table mapping without a codegen step
The ultimate ORM for Node and Typescript
At a glance
- What is it?
- Orange ORM is an object relational mapper for Node, Bun and Deno that describes tables in TypeScript or JavaScript and needs no code generation step. It covers a wide set of database engines, but the runtime and database support matrix is uneven, and the project is maintained by one person in his spare time.
- Who is it for?
- Orange ORM fits a team that wants an Active Record style API with TypeScript inference over the table definitions themselves, and that will accept owning the generated SQL when a query misbehaves. It is a poor fit for an application that needs a proven query builder for a complex reporting workload, or for a codebase that requires a documented path from the current major version.
- Can I use it commercially?
- Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Orange ORM describes tables instead of generating them
The problem Orange ORM addresses is the classic ORM setup tax. Most mappers want a schema file, a generation step, and a set of model classes that drift from the database. Orange's answer is to write the schema as code in one file and read that file directly at runtime.
The consequence is that there is no code generation step to run, and the project states that this keeps full IntelliSense in the table mappings themselves rather than in generated output. The mapping is the single source of truth, and the same file drives the query layer.
The style is the Active Record pattern: an object represents a row, and operations such as fetching, filtering and saving are methods on the mapped table. The querying model is described as rich and expressive, and relations are traversed in the query rather than loaded in a second round trip, which is what the filter example in the project documentation demonstrates.
The target audience is a TypeScript or JavaScript developer working with SQL who wants the Active Record ergonomics of Rails without leaving the Node ecosystem. The package supports both CommonJS and ECMAScript builds, and the published package exposes separate entry points for the server, the browser and the type declarations.
A mapping file declares columns, keys and relations
The schema is defined with `orange.map()`, where each table calls `column()` for its fields and the column methods encode type and key information. A small slice of the example in the project documentation looks like this:
import orange from 'orange-orm';
const map = orange.map(x => ({
customer: x.table('customer').map(({ column }) => ({
id: column('id').numeric().primary().notNullExceptInsert(),
name: column('name').string(),
balance: column('balance').numeric(),
isActive: column('isActive').boolean(),
})),
}));Note what `notNullExceptInsert()` means in practice. It marks a column as not null in the database while allowing the insert to omit it, which is how an auto-generated key behaves in a client that has to send something before the value exists. The chain of type methods (`.numeric()`, `.string()`, `.boolean()`, `.date()`) is what gives the editor its inference, and the primary key is declared with `.primary()`.
Relations are expressed on the same object, using `hasOne`, `hasMany` and `references` with a `.by()` call naming the foreign key column. The mapping in the documentation links an order to a customer, to many order lines and to one delivery address, and links an order line to many packages. A `.enum()` method constrains a column to a fixed set of string values, used in the example for country codes.
This is all declarative metadata. Nothing here opens a connection, which is why the same file can be shared between server and browser builds.
Table objects carry the queries, not a separate query builder
Once a map exists, the database handle is created by picking a driver on it. The example in the documentation opens a SQLite database and then works with rows through the mapped table:
import map from './map';
const db = map.sqlite('demo.db');
async function updateRow() {
const order = await db.order.getById(2, {
lines: true
});
order.lines.push({
product: 'broomstick',
amount: 300
});
await order.saveChanges();
}Two mechanisms are visible here. The first is eager loading by relation name: passing `lines: true` to `getById` fetches the related rows along with the parent. The second is mutation through the loaded graph, where a child row is pushed onto the collection and `saveChanges()` writes the change back.
Filtering works through a callback that receives the query object, and the condition can span relations:
const orders = await db.order.getMany({
where: x => x.lines.any(line => line.product.contains('broomstick'))
.and(x.customer.name.startsWith('Harry')),
lines: {
packages: true
},
deliveryAddress: true,
customer: true
});The `any` operator over a relation is the interesting part, since it turns a nested condition into a single query rather than loading every line and filtering in JavaScript. The nested `lines: { packages: true }` form loads a relation of a relation, which means the join depth of a query is under the developer's control.
Nine engines, four runtimes, and gaps in the matrix
The support table is the most useful thing to check before committing to this library, and it is not uniformly filled. Postgres, PGlite, MySQL, MariaDB, Oracle and SQLite carry checkmarks for Node, Deno and Bun. MS SQL is marked for Node and Bun but not Deno. SAP ASE is marked for Node only. Cloudflare D1 is marked for Cloudflare only, and PGlite is the entry that also carries a Web checkmark, alongside the Node, Deno and Bun columns.
In other words, the widest support belongs to PGlite, and the exotic engines are narrow. A team on Deno cannot use the MS SQL driver, and a Bun user cannot use SAP ASE. Cloudflare D1 sits in a different column entirely, which reflects that it runs on a worker rather than on a server runtime.
The runtime coverage is unusually broad for a database library, and the test scripts in the package manifest reflect that commitment, with separate targets for Node via vitest, for Bun with a thirty second timeout, and for Deno with the detect-cjs unstable flag. The cost of that breadth is that a behaviour verified on Node may not hold on Deno, and the published package targets three runtimes plus a browser build.
The browser build goes through the Express.js or Hono plugin
Running a database mapper in a browser sounds impossible until you read the reason the project gives. The browser entry point is meant to be used with the Express.js or Hono plugin, which keeps the database credentials on the server and exposes a REST-like interface to the client, with TypeScript tooling on top.
The project describes this as a way to use Orange securely in the browser, guarding credentials from client-side exposure and protecting against SQL injection. That claim rests on the plugin architecture rather than on anything in the mapping layer, and the practical reading is that the browser build talks to a server that owns the connection.
The published package reflects this split. The `exports` map points the browser condition at a separate bundle:
"exports": {
".": {
"types": "./src/index.d.ts",
"browser": "./dist/index.browser.mjs",
"import": "./dist/index.mjs",
"require": "./src/index.js"
}
}One detail is worth flagging. The `types` condition points at `./src/index.d.ts`, so type information is shipped as source rather than as a compiled declaration bundle, and the `files` list includes `src/`, `dist/`, `docs/`, `bin/` and the repository documentation files. Anyone who copies the mapping file into a browser project inherits those type definitions along with it.
Installing orange-orm pulls in a CLI as well as the library
Installation is a single npm command, and the package is published publicly:
npm install orange-ormThe package is more than a library import. It registers a binary named `orange-orm` that points at `bin/rdb.js`, so the same install gives you a command line tool alongside the importable API. The documentation does not spell out what every subcommand of that binary does, so its capabilities have to be discovered from the package itself.
The package also ships a `SKILL.md` and a `SECURITY.md` in the published files, which suggests the maintainer expects the library to be used by AI coding tools as well as by people. That fits with the MCP support described in the project documentation: Orange ORM is available as an MCP resource on Context7, so tools such as GitHub Copilot, Cursor or Claude can pull current documentation and code examples for it.
The documentation also points to a tutorial video, a relations diagram in `docs/`, and a Discord channel, all of which are reasonable places to look for the query model beyond the short example shown above.
One maintainer, ISC licence and a public versioning line
The project states plainly that it is free and open source, maintained in the author's spare time, and that sponsorship funds ongoing development, bug fixes, documentation and long-term maintenance. That is a direct statement about the bus factor, and it should be weighed by anyone considering the library for a long-lived product.
The version line shows a pace that is easy to judge from the release list. v5.4.0 shipped on 2026-07-09, v5.4.2 on 2026-08-09, and v5.5.0 on 2026-09-05, so the current major is five and the minor line moves every few weeks. The repository is not archived and the last push was on 2026-10-19, which is recent work against the 5.5.0 line.
The licence is ISC, a short permissive licence. It permits use, modification and redistribution, including inside a commercial product, and the obligations are limited to retaining the copyright notice and permission notice. That matters for a library that will be embedded in an application rather than run beside it. The repository also carries a code of conduct, a contributing guide and a security policy, so the usual project hygiene files are in place.
Editorial conclusion
Orange ORM fits a team that wants an Active Record style API with TypeScript inference over the table definitions themselves, and that will accept owning the generated SQL when a query misbehaves. It is a poor fit for an application that needs a proven query builder for a complex reporting workload, or for a codebase that requires a documented path from the current major version. Check first that your database and runtime appear in the support table, then confirm the query plan for a heavy join, since the mapper builds the SQL itself rather than delegating to an established query layer.
Frequently asked questions
Does Orange ORM need a code generation step?
No. The project states that there is no code generation required, and that table mappings keep full IntelliSense without one. The schema is written as a `map()` call in a TypeScript or JavaScript file that is read at runtime.
Which databases and runtimes does Orange ORM support?
The support table marks Node, Deno and Bun for Postgres, PGlite, MySQL, MariaDB, Oracle and SQLite. MS SQL is marked for Node and Bun only, SAP ASE for Node only, and Cloudflare D1 for Cloudflare only.
How do I install Orange ORM?
Run `npm install orange-orm`. The package registers a `orange-orm` binary pointing at `bin/rdb.js` in addition to the library import, and it ships separate entry points for CommonJS, ECMAScript and the browser.
Can Orange ORM query across relations?
Yes. The filter example in the documentation uses `x.lines.any(line => line.product.contains('broomstick'))` combined with `.and(x.customer.name.startsWith('Harry'))`, and related rows are loaded eagerly by naming them, for example `lines: true` or a nested `lines: { packages: true }`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/alfateam-orange-orm)Was this useful?