mondel
v0.2.1
Published
Lightweight TypeScript ORM for MongoDB - Type-safe, serverless-optimized
Maintainers
Readme
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: "[email protected]",
name: "John Doe",
role: "USER",
});
// Read with type-safe field selection
const found = await db.users.findOne(
{ email: "[email protected]" },
{ select: { _id: true, email: true, name: true } }
);
// Update with MongoDB native options
await db.users.updateOne(
{ email: "[email protected]" },
{ $set: { name: "John Smith" } },
{ upsert: true }
);
// Delete
await db.users.deleteOne({ email: "[email protected]" });
// 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: "[email protected]",
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: "[email protected]", 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
