npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ioni/nest-ts-valid-mongodb

v0.2.0

Published

High-performance MongoDB Native Driver wrapper for NestJS with Zod schema validation. Lightweight, type-safe, and robust

Readme

@ioni/nest-ts-valid-mongodb

NPM Version License Node.js Version TypeScript NestJS

Architect's Choice: High-performance MongoDB Native Driver wrapper for NestJS with Zod schema validation. Lightweight, type-safe, and robust.

🧠 Motivation

Let's be real: Mongoose is heavy. It adds a massive layer of abstraction, hydration, and state management that you often don't need for high-performance microservices.

On the other hand, using the Native MongoDB Driver directly is like running with scissors—fast, but one typo in your query or schema and you're in trouble.

@ioni/nest-ts-valid-mongodb bridges this gap. It provides:

  1. Native Speed: Zero-overhead wrapper around the official MongoDB driver.
  2. Zod Safety: Runtime validation using Zod schemas. If it doesn't match the schema, it doesn't get saved.
  3. NestJS Ergonomics: Proper Dependency Injection, Modules, and Decorators.

✨ Features

  • 🚀 Lightweight: ~8KB bundle size. No bloat.
  • 🛡️ Zod Powered: Define schemas with Zod, get TypeScript inference for free.
  • 💉 Dependency Injection: forRoot, forRootAsync (ConfigService), and forFeature.
  • 🔌 Robust: Auto-reconnect patterns and clean shutdown handling.
  • 🎨 Decorators: @InjectModel() and @InjectConnection() for clean code.

📦 Installation

pnpm add @ioni/nest-ts-valid-mongodb mongodb zod @nestjs/common @nestjs/core
# or
npm install @ioni/nest-ts-valid-mongodb mongodb zod @nestjs/common @nestjs/core

Note for Local Development: If you are using a local MongoDB instance (especially with Docker or ReplSets), you might need to append ?directConnection=true to your connection string to avoid connection timeouts.

Requirements

  • Node.js: >= 16.0.0
  • NestJS: ^10.0.0 || ^11.0.0
  • MongoDB: ^5.0.0 || ^6.0.0
  • TypeScript: >= 5.0.0
  • Zod: ^3.0.0

🏗️ Complete Example

Here's a full working example showing all the pieces together:

// user.schema.ts
import { z } from 'zod';

export const UserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().min(18),
  role: z.enum(['user', 'admin']).default('user'),
});

export type User = z.infer<typeof UserSchema>;

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TsValidMongoModule } from '@ioni/nest-ts-valid-mongodb';
import { UsersModule } from './users/users.module';

@Module({
  imports: [
    ConfigModule.forRoot(),
    TsValidMongoModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        uri: config.get<string>('MONGO_URI')!,
        databaseName: config.get<string>('MONGO_DB_NAME')!,
      }),
      inject: [ConfigService],
    }),
    UsersModule,
  ],
})
export class AppModule {}

// users/users.module.ts
import { Module } from '@nestjs/common';
import { TsValidMongoModule } from '@ioni/nest-ts-valid-mongodb';
import { UserSchema } from '../user.schema';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';

@Module({
  imports: [
    TsValidMongoModule.forFeature([
      {
        name: 'users',
        schema: UserSchema,
        indexes: [{ key: { email: 1 }, unique: true }],
      },
    ]),
  ],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

// users/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel, Model, Doc } from '@ioni/nest-ts-valid-mongodb';
import { User } from '../user.schema';

@Injectable()
export class UsersService {
  constructor(@InjectModel('users') private readonly usersModel: Model<User>) {}

  async create(user: User): Promise<Doc<User>> {
    return this.usersModel.insert(user);
  }

  async findAll(): Promise<Doc<User>[]> {
    return this.usersModel.find();
  }

  async findByEmail(email: string): Promise<Doc<User> | null> {
    return this.usersModel.findOneBy({ email });
  }

  async updateRole(id: string, role: 'user' | 'admin'): Promise<Doc<User> | null> {
    return this.usersModel.updateById(id, { values: { role } });
  }

  async delete(id: string): Promise<Doc<User> | null> {
    return this.usersModel.deleteById(id);
  }
}

// users/users.controller.ts
import { Controller, Get, Post, Body, Param, Delete, Put } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from '../user.schema';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  create(@Body() user: User) {
    return this.usersService.create(user);
  }

  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':email')
  findOne(@Param('email') email: string) {
    return this.usersService.findByEmail(email);
  }

  @Put(':id/role')
  updateRole(@Param('id') id: string, @Body('role') role: 'user' | 'admin') {
    return this.usersService.updateRole(id, role);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.usersService.delete(id);
  }
}

🚀 Quick Start

1. Define your Schema

Create a Zod schema. This acts as both your runtime validator and your TypeScript type source.

// cats.schema.ts
import { z } from 'zod';

export const CatSchema = z.object({
  name: z.string(),
  age: z.number().min(0),
  breed: z.string().optional(),
});

export type Cat = z.infer<typeof CatSchema>;

2. Import the Module

Register the module in your AppModule.

// app.module.ts
import { Module } from '@nestjs/common';
import { TsValidMongoModule } from '@ioni/nest-ts-valid-mongodb';
import { CatSchema } from './cats.schema';

@Module({
  imports: [
    // Synchronous configuration
    TsValidMongoModule.forRoot({
      uri: 'mongodb://localhost:27017',
      databaseName: 'test_db',
    }),

    // Register specific collections (features)
    TsValidMongoModule.forFeature([{ name: 'cats', schema: CatSchema }]),
  ],
})
export class AppModule {}

3. Inject and Use

Use the @InjectModel decorator to get your fully typed model.

// cats.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel, Model } from '@ioni/nest-ts-valid-mongodb';
import { Cat, CatSchema } from './cats.schema';

@Injectable()
export class CatsService {
  constructor(@InjectModel('cats') private readonly catModel: Model<Cat>) {}

  async create(cat: Cat) {
    // This will throw if 'cat' doesn't match the Zod schema!
    return await this.catModel.insert(cat);
  }

  async findAll() {
    return await this.catModel.find({});
  }

  async findByName(name: string) {
    return await this.catModel.findOneBy({ name });
  }

  async updateAge(id: string, age: number) {
    return await this.catModel.updateById(id, {
      values: { age },
    });
  }

  async delete(id: string) {
    return await this.catModel.deleteById(id);
  }
}

📘 API Reference

Model Methods

The injected Model<T> provides the following type-safe methods:

Query Methods

  • find(filters?, options?) - Find multiple documents
  • findOneBy(filters?, options?) - Find a single document
  • findById(id, options?) - Find by ObjectId or string ID
  • count(filters?, options?) - Count documents matching filters
  • advancedFind(config, outputSchema?, options?) - Advanced queries with cursor manipulation

Insert Methods

  • insert(document, options?) - Insert a single document (validates with Zod)
  • insertMany(documents, options?) - Insert multiple documents

Update Methods

  • updateById(id, { values, mode? }, options?) - Update by ID
  • updateOneBy({ values, mode? }, filters, options?) - Update first matching document
  • updateMany({ values, mode? }, filters?, options?) - Update multiple documents

The mode parameter supports:

  • 'basic' (default): Partial updates with type safety
  • 'advanced': Full MongoDB update operators ($set, $inc, etc.)

Delete Methods

  • delete(filters?, options?) - Delete multiple documents
  • deleteOneBy(filters, options?) - Delete first matching document
  • deleteById(id, options?) - Delete by ID

Type Utilities

import { Model, Doc } from '@ioni/nest-ts-valid-mongodb';
import { z } from 'zod';

const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>; // { name: string; email: string }
type UserDoc = Doc<User>; // User & { _id: ObjectId; __v?: number }
type UserModel = Model<User>; // Typed model with all CRUD methods

⚙️ Advanced Configuration

Async Configuration (ConfigService)

Usually, you don't hardcode credentials. Use forRootAsync:

TsValidMongoModule.forRootAsync({
  useFactory: async (configService: ConfigService) => ({
    uri: configService.get<string>('MONGO_URI'),
    databaseName: configService.get<string>('MONGO_DB_NAME'),
  }),
  inject: [ConfigService],
});

Accessing the Native Connection

Sometimes you need raw access to the connection (e.g., for transactions or admin operations).

import { Injectable } from '@nestjs/common';
import { InjectConnection } from '@ioni/nest-ts-valid-mongodb';
import type { MongoDbClientWrapper } from '@ioni/nest-ts-valid-mongodb';

@Injectable()
export class DatabaseService {
  constructor(@InjectConnection() private readonly dbWrapper: MongoDbClientWrapper) {}

  async getStats() {
    const db = this.dbWrapper.client.getDb();
    return await db.stats();
  }

  async createIndex() {
    const db = this.dbWrapper.client.getDb();
    return await db.collection('users').createIndex({ email: 1 }, { unique: true });
  }

  async runTransaction(operations: () => Promise<void>) {
    const session = await this.dbWrapper.client.getDb().client.startSession();
    try {
      await session.withTransaction(operations);
    } finally {
      await session.endSession();
    }
  }
}

🛡️ Error Handling

The library provides custom error classes for better error management:

Error Types

import {
  TsValidMongoError,
  TsValidMongoConnectionError,
  TsValidMongoConfigurationError,
} from '@ioni/nest-ts-valid-mongodb';
  • TsValidMongoError - Base error class for all library errors
  • TsValidMongoConnectionError - Thrown when connection fails
  • TsValidMongoConfigurationError - Thrown when configuration is invalid

Example: Handling Errors

import { Injectable, OnModuleInit } from '@nestjs/common';
import { InjectConnection, TsValidMongoConnectionError } from '@ioni/nest-ts-valid-mongodb';

@Injectable()
export class HealthService implements OnModuleInit {
  constructor(@InjectConnection() private dbWrapper: any) {}

  async onModuleInit() {
    try {
      const db = this.dbWrapper.client.getDb();
      await db.command({ ping: 1 });
      console.log('✅ Database connection healthy');
    } catch (error) {
      if (error instanceof TsValidMongoConnectionError) {
        console.error('❌ Database connection failed:', error.message);
        console.error('Cause:', error.cause);
      }
      throw error;
    }
  }
}

🔄 Graceful Shutdown

Production applications must handle shutdown gracefully to prevent data loss and connection leaks. This is especially critical in containerized environments (Docker, Kubernetes) where pods are frequently terminated.

Why Graceful Shutdown Matters

When your NestJS application receives a termination signal (SIGTERM, SIGINT), MongoDB connections must be closed properly to:

  • Prevent connection leaks that exhaust the connection pool
  • Avoid orphaned connections in testing environments
  • Ensure clean pod termination in Kubernetes (respecting terminationGracePeriodSeconds)
  • Allow in-flight operations to complete before shutdown

Enabling Graceful Shutdown

Enable shutdown hooks in your main.ts:

// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Enable graceful shutdown hooks
  // This ensures MongoDB connections are properly closed on SIGTERM/SIGINT
  app.enableShutdownHooks();

  await app.listen(3000);
}
bootstrap();

Configuration Options

You can customize shutdown behavior when configuring the module (Note: These settings are applied at application startup and cannot be changed dynamically):

TsValidMongoModule.forRoot({
  uri: 'mongodb://localhost:27017',
  databaseName: 'my_database',

  // Optional: Graceful shutdown configuration
  shutdownTimeout: 10000,  // Max time for shutdown process (default: 10000ms)
  forceShutdown: false,    // Wait for operations vs force close (default: false)
})

// Example: Faster shutdown with force close (use with caution)
TsValidMongoModule.forRoot({
  uri: 'mongodb://localhost:27017',
  databaseName: 'my_database',
  shutdownTimeout: 5000,   // Shorter timeout for dev/test environments
  forceShutdown: true,     // Force immediate close (may interrupt operations)
})

Configuration Parameters

  • shutdownTimeout (number, optional)

    • Maximum time to wait for the shutdown process to complete
    • Applies to each connection close operation independently
    • Default: 10000 ms (10 seconds)
    • Must be a positive finite number
    • Set to 0 for no timeout (not recommended in production)
  • forceShutdown (boolean, optional)

    • Whether to force-close MongoDB connections (drops pending operations immediately)
    • Default: false (graceful close - waits for in-flight operations to complete)
    • When true: Immediately closes connections, may interrupt active operations
    • When false: Waits for pending operations before closing
    • Use true only when you need immediate termination regardless of data integrity

Kubernetes Integration

For Kubernetes deployments, ensure your terminationGracePeriodSeconds exceeds your shutdown timeout:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nestjs-app
spec:
  template:
    spec:
      terminationGracePeriodSeconds: 30  # Should be > shutdownTimeout
      containers:
        - name: app
          image: my-app:latest
          # ... other config

Structured Logging

The shutdown process emits structured JSON logs compatible with observability platforms (Datadog, Splunk, etc.).

Pro Tip: By default, NestJS wraps logs in its own text format. To get pure JSON output suitable for log aggregators, we recommend using a JSON-logger compatible with NestJS, such as nestjs-pino.

// Shutdown initiated
{
  "event": "shutdown.start",
  "connectionCount": 2,
  "timestamp": "2025-01-15T10:30:00.000Z"
}

// Connection closed successfully
{
  "event": "connection.closed",
  "token": "default",
  "durationMs": 45,
  "timestamp": "2025-01-15T10:30:00.050Z"
}

// Shutdown completed
{
  "event": "shutdown.complete",
  "totalConnections": 2,
  "successCount": 2,
  "failureCount": 0,
  "durationMs": 95,
  "timestamp": "2025-01-15T10:30:00.100Z"
}

Shutdown Behavior

The shutdown process includes built-in resilience features:

  • Automatic retries: Failed connection closes are retried up to 2 times with exponential backoff
  • Parallel execution: All connections close concurrently for faster shutdown
  • Timeout enforcement: Each connection has an independent timeout to prevent hanging
  • Graceful degradation: Individual connection failures don't block the shutdown process

Best Practices

  1. Always enable shutdown hooks (app.enableShutdownHooks()) in production environments
  2. Use graceful close by default: Keep forceShutdown: false unless you have specific requirements
  3. Set appropriate timeout based on your application's workload (typically 5-30 seconds)
  4. Monitor shutdown logs to detect slow or failing connection closures using structured JSON output
  5. Test shutdown behavior in your integration tests to ensure connections close cleanly
  6. Avoid force shutdown unless absolutely necessary - it may cause data inconsistencies
  7. Coordinate with Kubernetes terminationGracePeriodSeconds (k8s timeout should be higher than shutdownTimeout)

🤝 Contributing

We hate bad code. If you want to contribute, ensure:

  1. Strict Types: No any (unless you have a damn good reason).
  2. Tests: Unit tests are mandatory.
  3. Linting: Respect the eslint rules.

🐛 Feedback & Support

This is an actively maintained project. We welcome feedback, bug reports, and feature requests:

📈 Roadmap

  • [ ] Comprehensive test suite with 80%+ coverage
  • [ ] Multi-tenant connection pools support
  • [ ] Performance benchmarks vs Mongoose
  • [ ] Support for MongoDB change streams
  • [ ] Advanced aggregation pipeline helpers
  • [ ] Schema migration utilities

📚 Related Projects

  • ts-valid-mongodb - The underlying MongoDB wrapper
  • Zod - TypeScript-first schema validation
  • NestJS - Progressive Node.js framework

🙏 Acknowledgments

This library is a robust NestJS wrapper for ts-valid-mongodb. We stand on the shoulders of giants—special thanks to the original authors for creating such a lightweight and type-safe foundation for MongoDB interactions.

📄 License

MIT © Ioni

See LICENSE for full details.