MongoatODM productivity, without giving up the driver.
Type-safe models, server-side validation, typed hooks, and production-ready migrations — layered on the official MongoDB driver at zero measured overhead.
Type-safe models, server-side validation, typed hooks, and production-ready migrations — layered on the official MongoDB driver at zero measured overhead.
Versioned migrations with a distributed lock, transactional runs, and a CLI built for CI — dry-run, status --json, tiered exit codes.
Define validation as a plain object, or as a class with @Schema/@Prop — both compile to the same server-side validator.
JSON Schema ($jsonSchema) validation enforced by MongoDB itself at collection level — invalid writes are rejected by the server, not just the client.
Benchmarked against the raw driver on ten operations — mongoat's throughput sits within measurement noise of native on every one, while Mongoose pays ≈3× on batch writes.
See the benchmark →A modern API layered on top of the official MongoDB driver — the driver is never hidden, always accessible.
Transform documents before insert/update or react after any operation, with typed contexts per method.
An always-on $where guard and opt-in filter sanitizing keep hostile query input away from the server.
Allow only the methods each model should expose — a Proxy blocks everything else before the call even happens.
Drop down to the native Collection/Db/MongoClient any time — full control, no lock-in.
Pass { session } to any write — server-side validation, hooks, and method gating all run inside the transaction.
One model, two equivalent ways to describe it — a decorated class or a plain $jsonSchema object — and CRUD that stays on the driver's own filter and options types.
import { Model, Optional, Prop, Schema } from '@iamcalegari/mongoat';
@Schema('users')
class UserSchema {
@Prop({ bsonType: 'string', description: 'Username of the user' })
username!: string;
@Prop({
bsonType: 'string',
pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$',
})
mail!: string;
@Optional()
@Prop({ bsonType: ['int', 'null'] })
age?: number;
}
export const User = new Model<UserSchema>({
schema: UserSchema,
validity: true,
});import { Model, ModelValidationSchema } from '@iamcalegari/mongoat';
const schema: ModelValidationSchema = {
bsonType: 'object',
required: ['username', 'mail'],
properties: {
username: { bsonType: 'string', description: 'Username of the user' },
mail: {
bsonType: 'string',
pattern: '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$',
},
age: { bsonType: ['int', 'null'] },
},
};
export const User = new Model({
collectionName: 'users',
schema,
validity: true,
});import { Database } from '@iamcalegari/mongoat';
import { User } from './user-model';
const database = new Database({ dbName: 'my-app' });
await database.connect();
// Idempotent: applies server-side validators and indexes for every model.
await database.setupCollections();
const doc = await User.insert({ username: 'foobar', mail: 'foo@bar.com' });
const adults = await User.findMany({ age: { $gte: 18 } });Both schema tabs produce the same server-side validator — MongoDB itself rejects invalid writes, not just the client. Follow the getting started tutorial for the full walkthrough.
Every figure below comes from a reproducible benchmark of @iamcalegari/mongoat against the native MongoDB driver, Mongoose, and Papr — published npm releases at pinned versions, all driving the same pinned mongo:7 server, medians across rounds.
$where by defaultRead the full benchmark — method, noise floors, and raw numbers →