Using ODMs with DaloyJS
DaloyJS works just as well with document databases as it does with SQL databases, but the abstractions are different. Use an ODM when your persistence layer is document-shaped and you want schemas, validation, middleware, and query helpers around collections or buckets.
ORM vs ODM
- ORM maps relational tables and joins into TypeScript objects. Use it for PostgreSQL, MySQL, SQLite, MariaDB, or MSSQL.
- ODM maps JSON-like documents and collection workflows into TypeScript objects. Use it for document databases such as MongoDB or Couchbase.
The recommended pattern
Just like SQL clients, ODM connections belong in a plugin. Decorate your app with a small database surface and close the connection on shutdown.
// src/db/plugin.ts
import type { App } from "@daloyjs/core";
export function databasePlugin(db: Database) {
return {
name: "database",
async register(app: App) {
app.decorate("db", db);
app.onClose(async () => {
await db.disconnect();
});
},
};
}Pick your ODM
- Mongoose — mature schemas, middleware, validation, and session support for MongoDB.
- Ottoman — schema and model layer for Couchbase buckets, scopes, and collections.
Runtime compatibility cheat sheet
| ODM | Node.js | Bun | Deno | Cloudflare Workers | Vercel Edge |
|---|---|---|---|---|---|
| Mongoose | Yes | Partial | No | No | No |
| Ottoman | Yes | Partial | No | No | No |
Mongoose and Ottoman both depend on Node.js database drivers, so they are primarily Node.js choices. If you need a portable edge-friendly database layer, stay in the SQL-oriented ORM section and choose a compatible client there.
Typing the decorated client
// src/types/state.d.ts
import type { db } from "../db/mongoose";
declare module "@daloyjs/core" {
interface AppState {
db: typeof db;
}
}Sessions and transactions
MongoDB transactions require a replica set and a session. Start the session inside the handler that owns the unit of work, then pass it through each model operation.
handler: async ({ body, state }) => {
const session = await state.db.connection.startSession();
try {
let createdOrder: unknown;
await session.withTransaction(async () => {
createdOrder = await state.db.Order.create([{ ...body }], { session });
await state.db.Inventory.updateOne(
{ sku: body.sku },
{ $inc: { stock: -body.qty } },
{ session }
);
});
return { status: 201, body: createdOrder };
} finally {
await session.endSession();
}
}