Skip to content

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.

Mongoat

What it looks like

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.

ts
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,
});
ts
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,
});
ts
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.

Benchmarked, not assumed

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.

Dependencies over the driver
+0
Mongoat installs nothing beyond the mongodb driver you already pull in — 150 KB of its own code, zero extra packages to audit.
Runtime overhead vs. the driver
≈ 0
On all ten benchmarked operations, mongoat's throughput sits within measurement noise of the raw driver it wraps.
Batch writes vs. Mongoose
≈3× faster
insertMany(1000): mongoat holds native-level throughput while Mongoose drops to ~31% of the driver. findMany(100) tells the same story at ≈2×.
Blocks $where by default
1 of 4
Only mongoat stops server-side JavaScript injection out of the box. The native driver, Mongoose and Papr all run it.

Read the full benchmark — method, noise floors, and raw numbers →