Thin MongoDB ODM built for Standard Schema
mongodb zod deno
1# **Nozzle** 2 3A lightweight, type-safe ODM for MongoDB in TypeScript 4 5> **Note:** Nozzle requires MongoDB **4.2 or newer** and works best with the 6> latest stable MongoDB server (6.x or newer) and the official 7> [mongodb](https://www.npmjs.com/package/mongodb) Node.js driver (v6+). 8 9## ✨ Features 10 11- **Schema-first:** Define and validate collections using 12 [Zod](https://zod.dev). 13- **Type-safe operations:** Auto-complete and strict typings for `insert`, 14 `find`, `update`, and `delete`. 15- **Minimal & modular:** No decorators or magic. Just clean, composable APIs. 16- **Built on MongoDB native driver:** Zero overhead with full control. 17 18--- 19 20## 📦 Installation 21 22```bash 23deno add jsr:@nozzle/nozzle 24``` 25 26> If you need to upgrade your local MongoDB server, see: 27> https://www.mongodb.com/docs/manual/administration/install-community/ 28 29--- 30 31## 🚀 Quick Start 32 33### 1. Define a schema 34 35```ts 36// src/schemas/user.ts 37import { z } from "@zod/zod"; 38 39export const userSchema = z.object({ 40 name: z.string(), 41 email: z.email(), 42 age: z.number().int().positive().optional(), 43 createdAt: z.date().default(() => new Date()), 44}); 45 46export type User = z.infer<typeof userSchema>; 47``` 48 49--- 50 51### 2. Initialize connection and model 52 53```ts 54// src/index.ts 55import { connect, disconnect, InferModel, Input, Model } from "@nozzle/nozzle"; 56import { userSchema } from "./schemas/user"; 57import { ObjectId } from "mongodb"; // v6+ driver recommended 58 59type User = InferModel<typeof userSchema>; 60type UserInsert = Input<typeof userSchema>; 61 62async function main() { 63 // Basic connection 64 await connect("mongodb://localhost:27017", "your_database_name"); 65 66 // Or with connection pooling options 67 await connect("mongodb://localhost:27017", "your_database_name", { 68 maxPoolSize: 10, // Maximum connections in pool 69 minPoolSize: 2, // Minimum connections in pool 70 maxIdleTimeMS: 30000, // Close idle connections after 30s 71 connectTimeoutMS: 10000, // Connection timeout 72 socketTimeoutMS: 45000, // Socket timeout 73 }); 74 75 // Production-ready connection with retry logic and resilience 76 await connect("mongodb://localhost:27017", "your_database_name", { 77 // Connection pooling 78 maxPoolSize: 10, 79 minPoolSize: 2, 80 81 // Automatic retry logic (enabled by default) 82 retryReads: true, // Retry failed read operations 83 retryWrites: true, // Retry failed write operations 84 85 // Timeouts 86 connectTimeoutMS: 10000, // Initial connection timeout 87 socketTimeoutMS: 45000, // Socket operation timeout 88 serverSelectionTimeoutMS: 10000, // Server selection timeout 89 90 // Connection resilience 91 maxIdleTimeMS: 30000, // Close idle connections 92 heartbeatFrequencyMS: 10000, // Server health check interval 93 }); 94 95 const UserModel = new Model("users", userSchema); 96 97 // Your operations go here 98 99 await disconnect(); 100} 101 102main().catch(console.error); 103``` 104 105--- 106 107### 3. Perform operations 108 109```ts 110// Insert one 111// Note: createdAt has a default, so it's optional in the input type 112const newUser: UserInsert = { 113 name: "John Doe", 114 email: "john.doe@example.com", 115 age: 30, 116 // createdAt is optional because of z.date().default(() => new Date()) 117}; 118const insertResult = await UserModel.insertOne(newUser); 119 120// Find many 121const users = await UserModel.find({ name: "John Doe" }); 122 123// Find one 124const found = await UserModel.findOne({ 125 _id: new ObjectId(insertResult.insertedId), 126}); // ObjectId from mongodb v6+ 127 128// Update 129await UserModel.update({ name: "John Doe" }, { age: 31 }); 130 131// Delete 132await UserModel.delete({ name: "John Doe" }); 133 134// Insert many 135await UserModel.insertMany([ 136 { name: "Alice", email: "alice@example.com", age: 25 }, 137 { name: "Bob", email: "bob@example.com" }, 138]); 139 140// Find by ID 141await UserModel.findById(insertResult.insertedId); 142 143// Update one 144await UserModel.updateOne({ name: "Alice" }, { age: 26 }); 145 146// Replace one 147await UserModel.replaceOne({ name: "Bob" }, { 148 name: "Bob", 149 email: "bob@newmail.com", 150 age: 22, 151}); 152 153// Delete one 154await UserModel.deleteOne({ name: "Alice" }); 155 156// Count 157const count = await UserModel.count({ age: { $gte: 18 } }); 158 159// Aggregation 160const aggregation = await UserModel.aggregate([ 161 { $match: { age: { $gte: 18 } } }, 162 { $group: { _id: null, avgAge: { $avg: "$age" } } }, 163]); 164 165// Paginated query 166const paginated = await UserModel.findPaginated( 167 { age: { $gte: 18 } }, 168 { skip: 0, limit: 10, sort: { age: -1 } }, 169); 170 171// Index Management 172// Create a unique index 173await UserModel.createIndex({ email: 1 }, { unique: true }); 174 175// Create a compound index 176await UserModel.createIndex({ name: 1, age: -1 }); 177 178// Create multiple indexes at once 179await UserModel.createIndexes([ 180 { key: { email: 1 }, name: "email_idx", unique: true }, 181 { key: { name: 1, age: -1 }, name: "name_age_idx" }, 182]); 183 184// List all indexes 185const indexes = await UserModel.listIndexes(); 186console.log("Indexes:", indexes); 187 188// Check if index exists 189const exists = await UserModel.indexExists("email_idx"); 190 191// Drop an index 192await UserModel.dropIndex("email_idx"); 193 194// Sync indexes (useful for migrations - creates missing, updates changed) 195await UserModel.syncIndexes([ 196 { key: { email: 1 }, name: "email_idx", unique: true }, 197 { key: { createdAt: 1 }, name: "created_at_idx" }, 198]); 199 200// Transactions (Requires MongoDB Replica Set or Sharded Cluster) 201import { withTransaction } from "@nozzle/nozzle"; 202 203// Automatic transaction management with withTransaction 204const result = await withTransaction(async (session) => { 205 // All operations in this callback are part of the same transaction 206 const user = await UserModel.insertOne( 207 { name: "Alice", email: "alice@example.com" }, 208 { session }, // Pass session to each operation 209 ); 210 211 const order = await OrderModel.insertOne( 212 { userId: user.insertedId, total: 100 }, 213 { session }, 214 ); 215 216 // If any operation fails, the entire transaction is automatically aborted 217 // If callback succeeds, transaction is automatically committed 218 return { user, order }; 219}); 220 221// Manual session management (for advanced use cases) 222import { endSession, startSession } from "@nozzle/nozzle"; 223 224const session = startSession(); 225try { 226 await session.withTransaction(async () => { 227 await UserModel.insertOne({ name: "Bob", email: "bob@example.com" }, { 228 session, 229 }); 230 await UserModel.updateOne({ name: "Alice" }, { balance: 50 }, { session }); 231 }); 232} finally { 233 await endSession(session); 234} 235 236// Error Handling 237import { ConnectionError, ValidationError } from "@nozzle/nozzle"; 238 239try { 240 await UserModel.insertOne({ name: "", email: "invalid" }); 241} catch (error) { 242 if (error instanceof ValidationError) { 243 console.error("Validation failed:", error.operation); 244 // Get field-specific errors 245 const fieldErrors = error.getFieldErrors(); 246 console.error("Field errors:", fieldErrors); 247 // { name: ['String must contain at least 1 character(s)'], email: ['Invalid email'] } 248 } else if (error instanceof ConnectionError) { 249 console.error("Connection failed:", error.uri); 250 } else { 251 console.error("Unexpected error:", error); 252 } 253} 254``` 255 256--- 257 258## 🗺️ Roadmap 259 260### 🔴 Critical (Must Have) 261 262- [x] Transactions support 263- [x] Connection retry logic 264- [x] Improved error handling 265- [x] Connection health checks 266- [x] Connection pooling configuration 267 268### 🟡 Important (Should Have) 269 270- [x] Index management 271- [ ] Middleware/hooks system 272- [ ] Relationship/population support 273- [x] Better default value handling 274- [ ] Comprehensive edge case testing 275 276### 🟢 Nice to Have 277 278- [x] Pagination support 279- [ ] Plugin system 280- [ ] Query builder API 281- [ ] Virtual fields 282- [ ] Document/static methods 283 284For detailed production readiness assessment, see 285[PRODUCTION_READINESS_ASSESSMENT.md](./PRODUCTION_READINESS_ASSESSMENT.md). 286 287--- 288 289## 📄 License 290 291MIT — use it freely and contribute back if you'd like! 292 293---