Package Exports
- mondel
Readme
Mondel
Lightweight TypeScript ORM for MongoDB
Type-safe. Serverless-ready. Zero magic.
Installation • Quick Start • Features • API Reference • Documentation
Overview
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.
Features
- 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
Installation
npm install mondel mongodb zodyarn add mondel mongodb zodpnpm add mondel mongodb zodRequirements
- Node.js 18+
- MongoDB 6.0+
- TypeScript 5.0+ (recommended)
Quick Start
1. Define Your Schema
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;2. Create Client (Serverless Mode)
import { createClient, type SchemasToClient } from "mondel";
import { schemas } from "./schemas";
// Type for the connected client
export type DbClient = SchemasToClient<typeof schemas>;
// 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<DbClient> {
return connectDb(uri);
}3. Perform CRUD Operations
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();Type Safety
// ✅ 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 } });Schema Definition
Define your schemas with a fluent, type-safe builder:
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" },
},
],
});Field Types
| Type | Builder | Description |
|---|---|---|
| String | s.string() |
String field with optional validations |
| Number | s.number() |
Numeric field with min/max |
| Boolean | s.boolean() |
Boolean field |
| Date | s.date() |
Date field |
| ObjectId | s.objectId() |
MongoDB ObjectId |
| Array | s.array(items) |
Array of typed items |
| Object | s.object(props) |
Nested object |
| JSON | s.json() |
Arbitrary JSON data |
| Enum | s.enum([...]) |
String enum validation |
| Literal | s.literal(value) |
Literal value |
Field Modifiers
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 patternIndex Types
// 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" } }
]Multiple Connections
// 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({});Automatic Validation
Validation happens automatically inside CRUD methods - you don't need to call it manually:
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)
Transactions
Full support for MongoDB transactions via db.startSession():
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();
}API Reference
defineSchema(name, definition)
Creates a schema definition with full type inference.
createClient(config)
Creates a database client. Supports two modes:
Serverless Mode (recommended for Cloudflare Workers, Vercel Edge):
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):
const db = await createClient({
uri: process.env.MONGODB_URI,
schemas: [userSchema] as const,
syncIndexes: true,
validation: "strict",
});Collection Methods
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 |
Documentation
Full documentation is available at https://mondel-orm.pages.dev
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
License
MIT © Edjo
Made with ❤️ for the MongoDB community