Lightweight TypeScript ORM for MongoDB - Type-safe, serverless-optimized
npm install mondel

Lightweight TypeScript ORM for MongoDB
Type-safe. Serverless-ready. Zero magic.
Installation •
Quick Start •
Features •
API Reference •
Documentation
---
Mondel is a lightweight TypeScript ORM designed for MongoDB, optimized for serverless environments like Cloudflare Workers, Vercel Edge Functions, and AWS Lambda. It provides a 100% type-safe, intuitive API inspired by Prisma and Drizzle, with minimal bundle size and zero cold-start overhead.
- 100% Type-Safe — Schema names, field names, and return types strictly typed with full inference
- Serverless First — Optimized for Cloudflare Workers & Vercel Edge with minimal bundle size (~27KB)
- Zero Magic — No decorators, no reflection. Just pure TypeScript functions and predictability
- MongoDB Native — Full access to MongoDB driver options (upsert, sessions, transactions)
- Zod Integration — Built-in runtime validation with Zod schemas
- Intuitive API — Prisma-inspired CRUD operations, Drizzle-inspired schema definition
``bash`
npm install mondel mongodb zod
`bash`
yarn add mondel mongodb zod
`bash`
pnpm add mondel mongodb zod
- Node.js 18+
- MongoDB 6.0+
- TypeScript 5.0+ (recommended)
`typescript
import { defineSchema, s } from "mondel";
export const userSchema = defineSchema("users", {
timestamps: true,
fields: {
// _id is implicit - auto-generated by MongoDB and typed as ObjectId
email: s.string().required().unique().index({ name: "idx_email" }),
name: s.string(),
role: s.enum(["ADMIN", "USER", "GUEST"]).default("USER"),
age: s.number().min(0).max(150),
isActive: s.boolean().default(true),
},
});
// Export schemas array for client initialization
export const schemas = [userSchema] as const;
`
`typescript
import { createClient, type SchemasToClient } from "mondel";
import { schemas } from "./schemas";
// Type for the connected client
export type DbClient = SchemasToClient
// Create serverless client factory (no connection yet)
const connectDb = createClient({
serverless: true,
schemas,
syncIndexes: false,
validation: "strict",
});
// Connect when needed (e.g., in request handler)
export async function getDb(uri: string): Promise
return connectDb(uri);
}
`
`typescript
const db = await getDb(env.MONGODB_URI);
// Create
const result = await db.users.create({
email: "john@example.com",
name: "John Doe",
role: "USER",
});
// Read with type-safe field selection
const found = await db.users.findOne(
{ email: "john@example.com" },
{ select: { _id: true, email: true, name: true } }
);
// Update with MongoDB native options
await db.users.updateOne(
{ email: "john@example.com" },
{ $set: { name: "John Smith" } },
{ upsert: true }
);
// Delete
await db.users.deleteOne({ email: "john@example.com" });
// Close connection when done
await db.close();
`
`typescript
// ✅ Works - 'users' is a registered schema
db.users.findMany({});
// TypeScript error - 'rooms' doesn't exist
db.rooms.findMany({}); // Property 'rooms' does not exist
// TypeScript error - 'invalidField' doesn't exist
db.users.findMany({}, { select: { invalidField: true } });
`
Define your schemas with a fluent, type-safe builder:
`typescript
import { defineSchema, s } from "mondel";
const productSchema = defineSchema("products", {
timestamps: true,
fields: {
// _id is implicit - auto-generated by MongoDB
sku: s.string().required().index({ unique: true, name: "idx_sku" }),
name: s.string().required().index({ type: "text" }),
price: s.number().required().min(0),
stock: s.number().default(0),
category: s.string().required(),
tags: s.array(s.string()),
location: s
.object({
type: s.literal("Point"),
coordinates: s.array(s.number()),
})
.index({ type: "2dsphere", name: "idx_location" }),
},
indexes: [
{
fields: { category: 1, price: -1 },
options: { name: "idx_category_price" },
},
],
});
`
| Type | Builder | Description |
| -------- | ------------------ | -------------------------------------- |
| String | s.string() | String field with optional validations |s.number()
| Number | | Numeric field with min/max |s.boolean()
| Boolean | | Boolean field |s.date()
| Date | | Date field |s.objectId()
| ObjectId | | MongoDB ObjectId |s.array(items)
| Array | | Array of typed items |s.object(props)
| Object | | Nested object |s.json()
| JSON | | Arbitrary JSON data |s.enum([...])
| Enum | | String enum validation |s.literal(value)
| Literal | | Literal value |
`typescript`
s.string()
.required() // Field is mandatory
.unique() // Unique constraint
.default("value") // Default value
.index() // Create index
.index({ name: "idx" }) // Named index
.index({ type: "text" }) // Text index
.min(1)
.max(100) // Length constraints (string/number)
.email() // Email validation
.url() // URL validation
.pattern(/regex/); // Regex pattern
`typescript
// Simple index
email: s.string().index()
// Named index
email: s.string().index({ name: "idx_email" })
// Unique index
email: s.string().index({ unique: true })
// Text index (full-text search)
description: s.string().index({ type: "text" })
// Geospatial index
location: s.object({...}).index({ type: "2dsphere" })
// TTL index
expiresAt: s.date().index({ expireAfterSeconds: 3600 })
// Compound indexes (schema level)
indexes: [
{ fields: { category: 1, price: -1 }, options: { name: "idx_cat_price" } }
]
`
`typescript
// Create separate clients for different databases
const mainDb = await createClient({
uri: process.env.MAIN_DB_URI,
schemas: [userSchema, orderSchema] as const,
});
const analyticsDb = await createClient({
uri: process.env.ANALYTICS_DB_URI,
schemas: [eventSchema, metricSchema] as const,
syncIndexes: false, // Don't sync indexes on replica
});
// Use them independently
const users = await mainDb.users.findMany({});
const events = await analyticsDb.events.findMany({});
`
Validation happens automatically inside CRUD methods - you don't need to call it manually:
`typescript
const db = await createClient({
uri: "mongodb://localhost:27017/mydb",
schemas: [userSchema] as const,
validation: "strict", // "strict" | "loose" | "off"
});
// TypeScript catches type errors at compile time
await db.users.create({
email: "test@example.com",
name: 123, // ❌ TypeScript error: expected string
});
// Runtime validation catches invalid data
await db.users.create({
email: "invalid-email", // ❌ Runtime error: invalid email format
name: "Test",
});
`
Validation modes:
- "strict" (default): Throws error on validation failure"loose"
- : Logs warning but continues"off"
- : No runtime validation (TypeScript still enforces types)
Full support for MongoDB transactions via db.startSession():
`typescript
const db = await createClient({
uri: process.env.MONGODB_URI,
schemas: [userSchema, orderSchema] as const,
});
const session = db.startSession();
try {
await session.withTransaction(async () => {
const user = await db.users.create({ email: "john@example.com", balance: 100 }, { session });
await db.orders.create({ userId: user.insertedId, amount: 50 }, { session });
await db.users.updateById(user.insertedId, { $inc: { balance: -50 } }, { session });
});
} finally {
await session.endSession();
}
`
Creates a schema definition with full type inference.
Creates a database client. Supports two modes:
Serverless Mode (recommended for Cloudflare Workers, Vercel Edge):
`typescript
const connectDb = createClient({
serverless: true,
schemas: [userSchema] as const,
syncIndexes: false,
validation: "strict",
});
// Returns a factory function - call with URI to connect
const db = await connectDb(env.MONGODB_URI);
`
Node.js Mode (for traditional servers):
`typescript`
const db = await createClient({
uri: process.env.MONGODB_URI,
schemas: [userSchema] as const,
syncIndexes: true,
validation: "strict",
});
All methods accept MongoDB native options as the last parameter.
| Method | Description |
| ----------------------------------- | ------------------------- |
| findOne(where, options?) | Find single document |findMany(where?, options?)
| | Find multiple documents |findById(id)
| | Find by ObjectId |create(data, options?)
| | Insert single document |createMany(data[], options?)
| | Insert multiple documents |updateOne(where, data, options?)
| | Update single document |updateMany(where, data, options?)
| | Update multiple documents |updateById(id, data, options?)
| | Update by ObjectId |deleteOne(where)
| | Delete single document |deleteMany(where)
| | Delete multiple documents |deleteById(id)
| | Delete by ObjectId |count(where?)
| | Count documents |exists(where)
| | Check if document exists |aggregate(pipeline)` | Run aggregation pipeline |
|
Full documentation is available at https://mondel-orm.pages.dev
Contributions are welcome! Please read our Contributing Guide for details.
MIT © Edjo
---
Made with ❤️ for the MongoDB community