mongster
v0.2.0
Published
A better MongoDB ODM
Downloads
192
Maintainers
Readme
Mongster
A type-safe MongoDB ODM (Object Document Mapper) for TypeScript with schema validation, automatic index management, and intuitive query building.
Features
- 🔒 Fully Type-Safe - End-to-end TypeScript support with complete type inference
- 📝 Schema Validation - Runtime validation with automatic TypeScript type generation
- 🔍 Index Management - Automatic index synchronization with MongoDB
- 🎯 Query Builder - Fluent, type-safe query API with projection and filtering
- 🔄 Transactions - ACID transactions with automatic session management
- ⚡ Optimized Performance - Built with Bun, compatible with Node.js 18+
- 🕒 Timestamps - Automatic
createdAtandupdatedAtfield management - 🛡️ Error Handling - Comprehensive error types for better debugging
Installation
npm install mongster
# or
bun add mongster
# or
yarn add mongster
# or
pnpm add mongsterQuick Start
import { M, mongster, model } from "mongster";
// Define your schema
const userSchema = M.schema({
name: M.string(),
email: M.string(),
age: M.number().min(0).max(120),
address: M.object({
street: M.string(),
city: M.string(),
zip: M.number()
}).optional(),
tags: M.array(M.string()).optional()
}).withTimestamps();
// Create a model
const User = model("users", userSchema);
// Connect to MongoDB
await mongster.connect("mongodb://localhost:27017/mydb");
// Create documents
const user = await User.createOne({
name: "John Doe",
email: "[email protected]",
age: 30
});
// Query documents
const users = await User.find({ age: { $gte: 18 } })
.include(["name", "email"])
.limit(10);Schema Definition
Basic Types
import { M } from "mongster";
const schema = M.schema({
// Primitives
name: M.string(),
age: M.number(),
active: M.boolean(),
birthDate: M.date(),
// MongoDB Types
userId: M.objectId(),
data: M.binary(),
price: M.decimal128(),
// Optional fields
nickname: M.string().optional(),
// Default values
status: M.string().default("pending"),
createdAt: M.date().defaultFn(() => new Date()),
});Validation
const schema = M.schema({
// String validation
username: M.string().min(3).max(20),
email: M.string().match(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/),
role: M.string().enum(["admin", "user", "guest"]),
// Number validation
age: M.number().min(0).max(120),
score: M.number().enum([1, 2, 3, 4, 5]),
// Boolean
verified: M.boolean(),
});Nested Objects and Arrays
const schema = M.schema({
// Nested objects
address: M.object({
street: M.string(),
city: M.string(),
coordinates: M.object({
lat: M.number(),
lng: M.number()
})
}),
// Arrays
tags: M.array(M.string()),
scores: M.array(M.number()),
// Array of objects
contacts: M.array(
M.object({
type: M.string(),
value: M.string()
})
).optional(),
});Timestamps
const schema = M.schema({
name: M.string(),
email: M.string()
}).withTimestamps(); // Adds createdAt and updatedAtType Inference
import type { M } from "mongster";
const userSchema = M.schema({
name: M.string(),
age: M.number()
});
// Infer the output type (what you get from database)
type User = M.infer<typeof userSchema>;
// { name: string; age: number }
// Infer the input type (what you provide when creating)
type UserInput = M.inferInput<typeof userSchema>;
// { name: string; age: number }Index Management
Mongster automatically manages indexes for you, creating, updating, and optionally dropping indexes based on your schema definition.
const schema = M.schema({
email: M.string().uniqueIndex(), // Unique index
username: M.string().index(1), // Ascending index
lastLogin: M.date().index(-1), // Descending index
content: M.string().textIndex(), // Text index for full-text search
location: M.string().hashedIndex(), // Hashed index
expiresAt: M.date().ttl(3600), // TTL index (expires after 1 hour)
}).addIndex(
{ email: 1, username: 1 }, // Compound index
{ unique: true }
);
// Indexes are automatically synced when you connect
await mongster.connect(uri, {
autoIndex: { syncOnConnect: true }
});
// Or manually sync indexes
await User.syncIndexes();CRUD Operations
Create
// Insert one document
const result = await User.insertOne({
name: "Alice",
email: "[email protected]",
age: 25
});
// Create and return the document
const user = await User.createOne({
name: "Bob",
email: "[email protected]",
age: 30
});
// Insert multiple documents
const results = await User.insertMany([
{ name: "Charlie", email: "[email protected]", age: 28 },
{ name: "Diana", email: "[email protected]", age: 32 }
]);
// Create multiple and return documents
const users = await User.createMany([
{ name: "Eve", email: "[email protected]", age: 27 },
{ name: "Frank", email: "[email protected]", age: 35 }
]);Read
// Find one document
const user = await User.findOne({ email: "[email protected]" });
// Find multiple documents
const users = await User.find({ age: { $gte: 25 } });
// Find with query builder
const results = await User.find({ age: { $gte: 18 } })
.include(["name", "email"]) // Include specific fields
.exclude(["_id"]) // Exclude fields
.sort({ age: -1 }) // Sort descending by age
.skip(10)
.limit(20);
// Find by ID
const user = await User.findById(someObjectId);
// Count documents
const count = await User.countDocuments({ active: true });Update
// Update one document
const result = await User.updateOne(
{ email: "[email protected]" },
{ $set: { age: 26 } }
);
// Update multiple documents
const result = await User.updateMany(
{ age: { $lt: 18 } },
{ $set: { status: "minor" } }
);
// Find and update
const updatedUser = await User.findOneAndUpdate(
{ email: "[email protected]" },
{ $inc: { age: 1 } },
{ returnDocument: "after" }
);
// Replace document
const result = await User.replaceOne(
{ _id: someId },
{ name: "New Name", email: "[email protected]", age: 40 }
);Delete
// Delete one document
const result = await User.deleteOne({ email: "[email protected]" });
// Delete multiple documents
const result = await User.deleteMany({ age: { $lt: 18 } });
// Find and delete
const deletedUser = await User.findOneAndDelete({ email: "[email protected]" });Connection Management
import { MongsterClient } from "mongster";
// Using the default client
import { mongster } from "mongster";
await mongster.connect("mongodb://localhost:27017/mydb");
// Create a custom client
const client = new MongsterClient();
await client.connect("mongodb://localhost:27017/mydb", {
retryConnection: 3, // Retry connection 3 times
retryDelayMs: 500, // Wait 500ms between retries
autoIndex: { syncOnConnect: true }, // Sync indexes on connect
// ... other MongoDB client options
});
// Check connection
const isConnected = await client.ping();
// Disconnect
await client.disconnect();Transactions
Mongster provides built-in transaction support with automatic session management:
// Simple transaction
await client.transaction(async (ctx) => {
const user = await User.createOne({ name: "Alice", email: "[email protected]" }, ctx.session);
await Log.createOne({ userId: user._id, action: "created" }, ctx.session);
});
// Transaction with automatic session injection
await client.transaction(async (ctx) => {
const ScopedUser = ctx.use(User);
const ScopedLog = ctx.use(Log);
const user = await ScopedUser.createOne({ name: "Bob", email: "[email protected]" });
await ScopedLog.createOne({ userId: user._id, action: "created" });
});
// Transaction with options
await client.transaction(
async (ctx) => {
// ... transaction logic
},
{ readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } }
);
// Manual session management (advanced)
const session = await client.startSession();
try {
const user = await User.createOne({ name: "Charlie", email: "[email protected]" }, { session });
// ... more operations
} finally {
await session.endSession();
}Transactions automatically handle commit/rollback and session cleanup. Use ctx.use(model) for automatic session injection, or pass { session } manually to individual operations.
Advanced Filtering
Mongster provides type-safe filtering with MongoDB query operators:
// Comparison operators
await User.find({ age: { $eq: 25 } });
await User.find({ age: { $ne: 25 } });
await User.find({ age: { $gt: 25 } });
await User.find({ age: { $gte: 25 } });
await User.find({ age: { $lt: 25 } });
await User.find({ age: { $lte: 25 } });
await User.find({ age: { $in: [25, 30, 35] } });
await User.find({ age: { $nin: [25, 30] } });
// Logical operators
await User.find({ $and: [{ age: { $gte: 18 } }, { status: "active" }] });
await User.find({ $or: [{ age: { $lt: 18 } }, { status: "inactive" }] });
await User.find({ $not: { age: { $lt: 18 } } });
// Array operators
await User.find({ tags: { $all: ["javascript", "typescript"] } });
await User.find({ tags: { $elemMatch: { $eq: "mongodb" } } });
await User.find({ tags: { $size: 3 } });Error Handling
import { MError } from "mongster";
try {
const user = await User.createOne({
name: "Invalid",
age: 150 // Exceeds max value
});
} catch (error) {
if (error instanceof MError) {
console.error("Validation error:", error.message);
}
}Multiple Clients
You can create multiple client instances for different databases:
import { MongsterClient, model } from "mongster";
// Client for main database
const mainClient = new MongsterClient();
await mainClient.connect("mongodb://localhost:27017/main");
// Client for analytics database
const analyticsClient = new MongsterClient();
await analyticsClient.connect("mongodb://localhost:27017/analytics");
// Models for different clients
const User = mainClient.model("users", userSchema);
const Event = analyticsClient.model("events", eventSchema);API Reference
Schema Builder Methods
M.string()- String typeM.number()- Number typeM.boolean()- Boolean typeM.date()- Date typeM.objectId()- MongoDB ObjectId typeM.binary()- Binary data typeM.decimal128()- Decimal128 typeM.object(shape)- Nested objectM.array(schema)- Array of itemsM.union([...schemas])- Union type (one of)M.tuple([...schemas])- Tuple type (fixed-length array)
Schema Modifiers
.optional()- Make field optional.nullable()- Allow null values.default(value)- Set default value.defaultFn(fn)- Set default value from function.min(n)- Minimum value/length.max(n)- Maximum value/length.enum([...values])- Enum values.match(regex)- Pattern matching (strings).index(direction)- Create index (1 for ascending, -1 for descending).uniqueIndex()- Create unique index.sparseIndex()- Create sparse index.textIndex()- Create text index.hashedIndex()- Create hashed index.ttl(seconds)- Create TTL index (only for Date fields)
Model Methods
Create
insertOne(doc, options?)- Insert one documentinsertMany(docs, options?)- Insert multiple documentscreateOne(doc, options?)- Insert and return documentcreateMany(docs, options?)- Insert and return documents
Read
find(filter, options?)- Find documents with query builderfindOne(filter, options?)- Find one documentfindById(id, options?)- Find by ObjectIdcountDocuments(filter, options?)- Count documentsestimatedDocumentCount(options?)- Estimated countdistinct(field, filter?, options?)- Get distinct valuesaggregate(pipeline, options?)- Run aggregation pipeline
Update
updateOne(filter, update, options?)- Update one documentupdateMany(filter, update, options?)- Update multiple documentsreplaceOne(filter, doc, options?)- Replace documentfindOneAndUpdate(filter, update, options?)- Find and updatefindOneAndReplace(filter, doc, options?)- Find and replace
Delete
deleteOne(filter, options?)- Delete one documentdeleteMany(filter, options?)- Delete multiple documentsfindOneAndDelete(filter, options?)- Find and delete
Index Management
syncIndexes(options?)- Synchronize indexes with schemagetCollection()- Get underlying MongoDB collection
Client Methods
Connection
connect(uri?, options?)- Connect to MongoDBdisconnect()- Disconnect from MongoDBping()- Check connection status
Transactions
transaction(callback, options?)- Execute a transaction with automatic commit/rollbackstartSession(options?)- Start a manual MongoDB session
Models
model(name, schema)- Create a model instance
Contributing
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
License
MIT - see LICENSE for details
Links
- GitHub: IshmamR/mongster
- npm: mongster
- Issues: Report a bug
- Author: IshmamR
