Skip to content

sceka/mongo-type-safe

Repository files navigation

mongo-type-safe

Get compile-time safety for all your MongoDB CRUD operations, plus optional runtime validation using Zod. Runtime checks are enabled by default for extra safety, but can be disabled for zero runtime overhead.


Features

  • Type-safe CRUD: Compile-time type checking for all MongoDB operations
  • Zod integration: Use your Zod schemas as the single source of truth
  • With runtime validation: Enforces schema correctness at both compile-time and runtime using Zod.
  • Familiar API: Mirrors the official MongoDB Node.js driver

Installation

npm install mongo-type-safe zod mongodb

Quick Start

1. Recommended: Initialize db before importing models (Mongoose-like pattern)

db.ts

import { MongoClient } from "mongodb";
export const client = new MongoClient("mongodb://localhost:27017");

index.ts (entrypoint of your app)

import { client } from "./db";
await client.connect(); // Ensure connection before importing models
export const db = client.db("test-db");

// ...rest of your app

userModel.ts

import { z } from "zod";
import { createSafeCollection } from "mongo-type-safe";
import { db } from "./index"; // or "./db" if you export db there

export const userSchema = z.object({
	name: z.string(),
	age: z.number().optional(),
	email: z.string().email()
});

export const safeUsers = createSafeCollection(db.collection("users"), userSchema);

Anywhere in your app:

import { safeUsers } from "./userModel";

await safeUsers.insertOne({ name: "Marko", email: "marko@example.com" });
const user = await safeUsers.findOne({ name: "Marko" });

Note: This pattern ensures that your models always have a ready-to-use, type-safe collection. Just make sure the db connection is established before any model is imported.


2. Model-only approach (if you want to create the collection on demand)

userModel.ts

import { z } from "zod";

export const userSchema = z.object({
	name: z.string(),
	age: z.number().optional(),
	email: z.string().email()
});

In your route/service/handler:

import { MongoClient } from "mongodb";
import { createSafeCollection } from "mongo-type-safe";
import { userSchema } from "./userModel";

async function main() {
	const client = new MongoClient("mongodb://localhost:27017");
	await client.connect();
	const db = client.db("test-db");

	// Create the type-safe collection on demand
	const safeUsers = createSafeCollection(db.collection("users"), userSchema);

	await safeUsers.insertOne({ name: "Marko", email: "marko@example.com" });
	const user = await safeUsers.findOne({ name: "Marko" });
	await safeUsers.updateOne({ name: "Marko" }, { $set: { age: 30 } });
	await safeUsers.deleteOne({ name: "Marko" });

	client.close();
}

main();

3. Model exports ready-to-use collection (if you have a global db instance)

userModel.ts

import { z } from "zod";
import { createSafeCollection } from "mongo-type-safe";
import { db } from "./db"; // your global db instance

export const userSchema = z.object({
	name: z.string(),
	age: z.number().optional(),
	email: z.string().email()
});

export const safeUsers = createSafeCollection(db.collection("users"), userSchema);

Anywhere in your app:

import { safeUsers } from "./userModel";

await safeUsers.insertOne({ name: "Marko", email: "marko@example.com" });
const user = await safeUsers.findOne({ name: "Marko" });

Strict mode (optional)

By default, createSafeCollection validates all filters and update objects at runtime using the Zod schema you provide. This helps catch bugs early and keeps your data access safe and predictable.

✅ Default behavior (strict: true)

Runtime validation ensures only valid filter/update structures are allowed:

const users = createSafeCollection(collection, userSchema);

await users.insertOne({ name: "Marko", age: 23 }); // ✅ OK

// 👇 TypeScript would normally catch this at compile time,
// but we use `as any` to simulate invalid data at runtime.
await users.insertOne({ name: 23 as any, age: "Marko" as any }); // ❌ Throws: Invalid document: expected string, received number

NOTE: as any is used to bypass TypeScript so we can demonstrate runtime validation. Without it, TypeScript would block the error before runtime.

✅ Disable validation (strict: false)

const users = createSafeCollection(collection, userSchema, { strict: false });

await users.insertOne({ name: "Marko", age: 23 }); // ✅ allowed

// 👇 Still compiles because we forced the types,
// but now no runtime validation is applied either.
await users.insertOne({ name: 23 as any, age: "Marko" as any }); // ✅ no error thrown

API Overview

All methods are type-safe and mirror the MongoDB driver:

  • insertOne(doc, options?)
  • insertMany(docs, options?)
  • findOne(filter, options?)
  • find(filter, options?)
  • updateOne(filter, update, options?)
  • updateMany(filter, update, options?)
  • deleteOne(filter, options?)
  • deleteMany(filter, options?)
  • replaceOne(filter, newDocument, options?)
  • findOneAndUpdate(filter, update, options?)
  • findOneAndReplace(filter, newDocument, options?)
  • findOneAndDelete(filter, options?)
  • countDocuments(filter?, options?)
  • estimatedDocumentCount(options?)
  • distinct(key, filter?)
  • aggregate(pipeline, options?)

See the source for full method signatures and JSDoc documentation.


Type Safety

  • All filters, updates, and documents are checked at compile time against your Zod schema.
  • Invalid field names or types will cause TypeScript errors before you run your code.

Zod Integration

  • Use your Zod schemas to define the shape of your documents.
  • The wrapper automatically infers TypeScript types from your Zod schema.

License

MIT


About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published