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---