|
| 1 | +# Mizzle ORM |
| 2 | + |
| 3 | +A MongoDB ORM with exceptional developer experience, built for TypeScript. |
| 4 | + |
| 5 | +[](https://www.npmjs.com/package/mizzle-orm) |
| 6 | +[](https://opensource.org/licenses/MIT) |
| 7 | + |
| 8 | +## Features |
| 9 | + |
| 10 | +- **Perfect Type Inference** - S+ tier TypeScript support with zero `any` types |
| 11 | +- **Flexible Relations** - EMBED (denormalized), LOOKUP (virtual joins), and REFERENCE strategies |
| 12 | +- **Auto-updating Embeds** - Optional `keepFresh` mode keeps embedded data synchronized |
| 13 | +- **Intuitive API** - Clean, modern syntax with excellent IntelliSense |
| 14 | +- **Context Support** - Built-in multi-tenancy and auth context handling |
| 15 | +- **Transaction Support** - First-class transaction API |
| 16 | +- **Zero Runtime Overhead** - Compile-time type checking with minimal runtime cost |
| 17 | + |
| 18 | +## Installation |
| 19 | + |
| 20 | +```bash |
| 21 | +npm install mizzle-orm mongodb |
| 22 | +# or |
| 23 | +pnpm add mizzle-orm mongodb |
| 24 | +# or |
| 25 | +yarn add mizzle-orm mongodb |
| 26 | +``` |
| 27 | + |
| 28 | +**Requirements:** Node.js 18+ and MongoDB 5.0+ |
| 29 | + |
| 30 | +## Quick Start |
| 31 | + |
| 32 | +```typescript |
| 33 | +import { mizzle, defineSchema, mongoCollection } from 'mizzle-orm'; |
| 34 | +import { string, objectId, date } from 'mizzle-orm'; |
| 35 | +import { lookup } from 'mizzle-orm'; |
| 36 | + |
| 37 | +// Define collections |
| 38 | +const users = mongoCollection('users', { |
| 39 | + name: string(), |
| 40 | + email: string(), |
| 41 | + createdAt: date(), |
| 42 | +}); |
| 43 | + |
| 44 | +const posts = mongoCollection( |
| 45 | + 'posts', |
| 46 | + { |
| 47 | + title: string(), |
| 48 | + content: string(), |
| 49 | + authorId: objectId(), |
| 50 | + createdAt: date(), |
| 51 | + }, |
| 52 | + { |
| 53 | + relations: { |
| 54 | + author: lookup(users, { |
| 55 | + localField: 'authorId', |
| 56 | + foreignField: '_id', |
| 57 | + one: true, |
| 58 | + }), |
| 59 | + }, |
| 60 | + } |
| 61 | +); |
| 62 | + |
| 63 | +// Create schema and connect |
| 64 | +const schema = defineSchema({ users, posts }); |
| 65 | +const db = await mizzle({ |
| 66 | + uri: 'mongodb://localhost:27017', |
| 67 | + dbName: 'myapp', |
| 68 | + schema, |
| 69 | +}); |
| 70 | + |
| 71 | +// Create data |
| 72 | +const user = await db().users.create({ |
| 73 | + name: 'Alice', |
| 74 | + email: 'alice@example.com', |
| 75 | + createdAt: new Date(), |
| 76 | +}); |
| 77 | + |
| 78 | +const post = await db().posts.create({ |
| 79 | + title: 'Hello Mizzle!', |
| 80 | + content: 'My first post', |
| 81 | + authorId: user._id, |
| 82 | + createdAt: new Date(), |
| 83 | +}); |
| 84 | + |
| 85 | +// Query with perfect type inference |
| 86 | +const posts = await db().posts.findMany( |
| 87 | + {}, |
| 88 | + { |
| 89 | + include: { author: true }, |
| 90 | + } |
| 91 | +); |
| 92 | + |
| 93 | +// TypeScript knows exact types! |
| 94 | +posts[0].title; // string |
| 95 | +posts[0].author?.name; // string | undefined |
| 96 | +posts[0].author?.email; // string | undefined |
| 97 | +``` |
| 98 | + |
| 99 | +## Core Concepts |
| 100 | + |
| 101 | +### Relations |
| 102 | + |
| 103 | +Mizzle supports three relation strategies: |
| 104 | + |
| 105 | +#### 1. EMBED Relations (Recommended) |
| 106 | + |
| 107 | +Denormalize data for lightning-fast reads with optional auto-updates: |
| 108 | + |
| 109 | +```typescript |
| 110 | +import { embed } from 'mizzle-orm'; |
| 111 | + |
| 112 | +const posts = mongoCollection('posts', { |
| 113 | + title: string(), |
| 114 | + authorId: objectId(), |
| 115 | +}, { |
| 116 | + relations: { |
| 117 | + author: embed(users, { |
| 118 | + forward: { |
| 119 | + from: 'authorId', |
| 120 | + fields: ['name', 'email'], |
| 121 | + }, |
| 122 | + keepFresh: true, // Auto-update when user changes |
| 123 | + }), |
| 124 | + }, |
| 125 | +}); |
| 126 | + |
| 127 | +// Author data embedded automatically! |
| 128 | +const post = await db().posts.create({ |
| 129 | + title: 'Hello World', |
| 130 | + authorId: userId, |
| 131 | +}); |
| 132 | + |
| 133 | +console.log(post.author.name); // Direct access, no join needed |
| 134 | +``` |
| 135 | + |
| 136 | +**Benefits:** |
| 137 | +- Fast reads (no joins) |
| 138 | +- Simple queries |
| 139 | +- Optional auto-updates with `keepFresh` |
| 140 | +- Perfect for read-heavy workloads |
| 141 | + |
| 142 | +#### 2. LOOKUP Relations (Virtual Joins) |
| 143 | + |
| 144 | +Query-time joins using MongoDB `$lookup`: |
| 145 | + |
| 146 | +```typescript |
| 147 | +import { lookup } from 'mizzle-orm'; |
| 148 | + |
| 149 | +author: lookup(users, { |
| 150 | + localField: 'authorId', |
| 151 | + foreignField: '_id', |
| 152 | + one: true, |
| 153 | +}) |
| 154 | +``` |
| 155 | + |
| 156 | +**Benefits:** |
| 157 | +- Always fresh data |
| 158 | +- Less storage |
| 159 | +- Best for frequently-changing data |
| 160 | + |
| 161 | +#### 3. REFERENCE Relations (Validation) |
| 162 | + |
| 163 | +Validate referential integrity: |
| 164 | + |
| 165 | +```typescript |
| 166 | +import { reference } from 'mizzle-orm'; |
| 167 | + |
| 168 | +author: reference(users, { |
| 169 | + localField: 'authorId', |
| 170 | + foreignField: '_id', |
| 171 | +}) |
| 172 | +``` |
| 173 | + |
| 174 | +### Context & Multi-tenancy |
| 175 | + |
| 176 | +Pass context for auth and multi-tenancy: |
| 177 | + |
| 178 | +```typescript |
| 179 | +// With context |
| 180 | +const userPosts = await db({ |
| 181 | + user: { id: userId, role: 'admin' }, |
| 182 | + tenantId: 'acme-corp' |
| 183 | +}).posts.findMany({}); |
| 184 | + |
| 185 | +// Without context |
| 186 | +const allPosts = await db().posts.findMany({}); |
| 187 | +``` |
| 188 | + |
| 189 | +### Transactions |
| 190 | + |
| 191 | +Built-in transaction support: |
| 192 | + |
| 193 | +```typescript |
| 194 | +await db.tx({}, async (txDb) => { |
| 195 | + const user = await txDb().users.create({ name: 'Bob' }); |
| 196 | + const post = await txDb().posts.create({ |
| 197 | + title: 'New Post', |
| 198 | + authorId: user._id |
| 199 | + }); |
| 200 | + // Committed atomically |
| 201 | +}); |
| 202 | +``` |
| 203 | + |
| 204 | +## Advanced Features |
| 205 | + |
| 206 | +### Auto-updating Embeds |
| 207 | + |
| 208 | +Keep embedded data fresh automatically: |
| 209 | + |
| 210 | +```typescript |
| 211 | +author: embed(users, { |
| 212 | + forward: { |
| 213 | + from: 'authorId', |
| 214 | + fields: ['name', 'avatar'] |
| 215 | + }, |
| 216 | + keepFresh: true, // Updates automatically when user changes |
| 217 | +}) |
| 218 | +``` |
| 219 | + |
| 220 | +### Manual Refresh |
| 221 | + |
| 222 | +Refresh embeds on-demand: |
| 223 | + |
| 224 | +```typescript |
| 225 | +// Query-time refresh (read-only) |
| 226 | +const posts = await db().posts.findMany( |
| 227 | + { status: 'published' }, |
| 228 | + { refreshEmbeds: ['author'] } |
| 229 | +); |
| 230 | + |
| 231 | +// Batch refresh (persisted) |
| 232 | +await db().posts.refreshEmbeds('author', { |
| 233 | + filter: { updatedAt: { $lt: yesterday } }, |
| 234 | + batchSize: 100, |
| 235 | +}); |
| 236 | +``` |
| 237 | + |
| 238 | +### Nested Includes |
| 239 | + |
| 240 | +Unlimited depth with perfect type inference: |
| 241 | + |
| 242 | +```typescript |
| 243 | +const posts = await db().posts.findMany({}, { |
| 244 | + include: { |
| 245 | + author: { |
| 246 | + include: { |
| 247 | + organization: true |
| 248 | + } |
| 249 | + }, |
| 250 | + comments: { |
| 251 | + include: { |
| 252 | + user: true |
| 253 | + } |
| 254 | + } |
| 255 | + } |
| 256 | +}); |
| 257 | + |
| 258 | +// All types perfectly inferred! |
| 259 | +posts[0].author?.organization?.name // string | undefined |
| 260 | +posts[0].comments[0]?.user?.email // string | undefined |
| 261 | +``` |
| 262 | + |
| 263 | +## When to Use Each Relation Type |
| 264 | + |
| 265 | +| Scenario | Best Choice | Why | |
| 266 | +|----------|-------------|-----| |
| 267 | +| Blog post authors | EMBED + `keepFresh` | Fast reads, occasional updates | |
| 268 | +| E-commerce orders | EMBED (no auto-update) | Historical snapshot | |
| 269 | +| Real-time stock prices | LOOKUP | Always need latest data | |
| 270 | +| User permissions | LOOKUP | Changes frequently | |
| 271 | +| Tag clouds | EMBED + `keepFresh` | Fast display, rare changes | |
| 272 | + |
| 273 | +## Examples |
| 274 | + |
| 275 | +Check out the [examples directory](./examples) for comprehensive demonstrations: |
| 276 | + |
| 277 | +- [quickstart.ts](./examples/quickstart.ts) - 5-minute tutorial |
| 278 | +- [blog-with-embeds.ts](./examples/blog-with-embeds.ts) - Blog platform with auto-updating embeds |
| 279 | +- [ecommerce-orders.ts](./examples/ecommerce-orders.ts) - Order system with historical snapshots |
| 280 | +- [mizzle-api-example.ts](./examples/mizzle-api-example.ts) - Advanced usage patterns |
| 281 | + |
| 282 | +## Documentation |
| 283 | + |
| 284 | +- [Complete Documentation](./docs/README.md) - Full API reference and guides |
| 285 | +- [Embed Relations Guide](./docs/embeds-guide.md) - Deep dive into embed strategies |
| 286 | +- [Migration Guide](./docs/lookup-to-embed-migration.md) - Converting from lookups to embeds |
| 287 | +- [Performance at Scale](./docs/PERFORMANCE_AT_SCALE.md) - Guidelines for large schemas |
| 288 | + |
| 289 | +## API Overview |
| 290 | + |
| 291 | +### Collections |
| 292 | + |
| 293 | +```typescript |
| 294 | +// Create |
| 295 | +const user = await db().users.create({ name: 'Alice' }); |
| 296 | +const users = await db().users.createMany([...]); |
| 297 | + |
| 298 | +// Read |
| 299 | +const user = await db().users.findOne({ email: 'alice@example.com' }); |
| 300 | +const users = await db().users.findMany({ active: true }); |
| 301 | +const users = await db().users.findMany({}, { include: { posts: true } }); |
| 302 | + |
| 303 | +// Update |
| 304 | +await db().users.updateOne({ _id: userId }, { name: 'Alice Updated' }); |
| 305 | +await db().users.updateMany({ active: false }, { deleted: true }); |
| 306 | + |
| 307 | +// Delete |
| 308 | +await db().users.deleteOne({ _id: userId }); |
| 309 | +await db().users.deleteMany({ deleted: true }); |
| 310 | + |
| 311 | +// Aggregations |
| 312 | +const result = await db().users.aggregate([...]); |
| 313 | + |
| 314 | +// Raw access |
| 315 | +const collection = db().users.collection; // Native MongoDB collection |
| 316 | +``` |
| 317 | + |
| 318 | +### Database Instance |
| 319 | + |
| 320 | +```typescript |
| 321 | +db.schema // Collection definitions |
| 322 | +db.client // Raw MongoClient |
| 323 | +db.tx // Transaction helper |
| 324 | +db.close() // Cleanup connection |
| 325 | +``` |
| 326 | + |
| 327 | +## TypeScript Support |
| 328 | + |
| 329 | +Mizzle provides exceptional TypeScript support: |
| 330 | + |
| 331 | +- Zero `any` types in your queries |
| 332 | +- Perfect inference for nested includes |
| 333 | +- Compile-time safety for all operations |
| 334 | +- Full IntelliSense support |
| 335 | +- Type-safe filters and projections |
| 336 | + |
| 337 | +## Performance |
| 338 | + |
| 339 | +**Read Performance:** |
| 340 | +- EMBED: ~50-100ms for 1000 documents |
| 341 | +- LOOKUP: ~200-500ms for 1000 documents |
| 342 | + |
| 343 | +**Recommendation:** Use EMBED for read-heavy workloads, LOOKUP for write-heavy or when data changes frequently. |
| 344 | + |
| 345 | +## Contributing |
| 346 | + |
| 347 | +Contributions are welcome! Please check out our [GitHub repository](https://github.com/mizzle-dev/mizzle-orm). |
| 348 | + |
| 349 | +1. Fork the repository |
| 350 | +2. Create your feature branch |
| 351 | +3. Commit your changes |
| 352 | +4. Push to the branch |
| 353 | +5. Open a Pull Request |
| 354 | + |
| 355 | +## License |
| 356 | + |
| 357 | +MIT © [Mizzle Dev](https://github.com/mizzle-dev) |
| 358 | + |
| 359 | +## Links |
| 360 | + |
| 361 | +- [Documentation](https://orm.mizzle.dev) |
| 362 | +- [GitHub Repository](https://github.com/mizzle-dev/mizzle-orm) |
| 363 | +- [Issue Tracker](https://github.com/mizzle-dev/mizzle-orm/issues) |
| 364 | +- [NPM Package](https://www.npmjs.com/package/mizzle-orm) |
| 365 | + |
| 366 | +--- |
| 367 | + |
| 368 | +**Built with love for the MongoDB + TypeScript community** |
0 commit comments