Skip to content
NestJS ns database 4 min read

Mongoose Setup

Mongoose is the most popular Object Document Mapper (ODM) for MongoDB in the Node.js ecosystem, and NestJS ships a first-class integration through the @nestjs/mongoose package. It wraps Mongoose’s connection lifecycle in a dedicated module, exposes schemas and models through the dependency injection container, and gives you clean, async-friendly configuration. This page covers installing the package, opening a connection with MongooseModule.forRoot, configuring connection options synchronously and asynchronously, and wiring up multiple named connections.

Installing the packages

Install the NestJS Mongoose wrapper alongside Mongoose itself. Mongoose carries its own TypeScript definitions, so no separate @types package is needed.

npm install @nestjs/mongoose mongoose

If you plan to load the connection URI from environment variables (recommended), also install the config module:

npm install @nestjs/config

Connecting with MongooseModule.forRoot

The simplest way to connect is to import MongooseModule.forRoot() once in your root AppModule. The first argument is the MongoDB connection string; the optional second argument accepts Mongoose connection options.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost:27017/devcraftly'),
  ],
})
export class AppModule {}

NestJS opens the connection when the application bootstraps and gracefully closes it on shutdown. When the connection succeeds you will see Mongoose-backed providers become available to the rest of your app.

Output:

[Nest] 4821  - 06/14/2026, 10:22:14 AM   [InstanceLoader] MongooseModule dependencies initialized +6ms
[Nest] 4821  - 06/14/2026, 10:22:14 AM   [NestApplication] Nest application successfully started +12ms

Avoid hard-coding the connection string. Production credentials belong in environment variables loaded through @nestjs/config, never in source control.

Configuring connection options

The second argument to forRoot is a MongooseModuleOptions object that extends Mongoose’s native connect options. These tune buffering, pool size, timeouts, and the default database.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost:27017/devcraftly', {
      dbName: 'devcraftly',
      maxPoolSize: 20,
      minPoolSize: 2,
      serverSelectionTimeoutMS: 5000,
      socketTimeoutMS: 45000,
      autoIndex: false,
    }),
  ],
})
export class AppModule {}

The most frequently used options:

OptionTypePurpose
dbNamestringDatabase to use, overriding any name in the URI.
maxPoolSizenumberMaximum sockets the driver keeps open (default 100).
minPoolSizenumberMinimum sockets kept warm in the pool.
serverSelectionTimeoutMSnumberHow long to try selecting a server before erroring.
socketTimeoutMSnumberHow long a socket stays inactive before closing.
autoIndexbooleanBuild schema indexes on startup; disable in production.
retryWritesbooleanAutomatically retry certain write operations.

Set autoIndex: false in production. Building indexes on every boot is expensive on large collections; create indexes deliberately via a migration or one-off task instead.

Async configuration with forRootAsync

In real applications the connection string comes from configuration that isn’t available at module-definition time. Use MongooseModule.forRootAsync() to inject ConfigService (or any provider) and build the options at runtime.

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    MongooseModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        uri: config.getOrThrow<string>('MONGODB_URI'),
        dbName: config.get<string>('MONGODB_DB', 'devcraftly'),
        maxPoolSize: 20,
        serverSelectionTimeoutMS: 5000,
      }),
    }),
  ],
})
export class AppModule {}

The useFactory returns the same MongooseModuleOptions shape, with the connection string supplied as the uri property. getOrThrow fails fast at startup if the variable is missing, which is safer than silently connecting to the wrong database.

Reacting to the connection with connectionFactory

If you need to hook into the raw Mongoose Connection, for example to register global plugins or log connection events, supply a connectionFactory.

import { MongooseModule } from '@nestjs/mongoose';
import { Connection } from 'mongoose';

MongooseModule.forRootAsync({
  useFactory: () => ({
    uri: process.env.MONGODB_URI,
    connectionFactory: (connection: Connection) => {
      connection.on('connected', () => console.log('MongoDB connected'));
      connection.on('disconnected', () => console.warn('MongoDB lost'));
      connection.plugin(require('mongoose-autopopulate'));
      return connection;
    },
  }),
});

Output:

MongoDB connected

Multiple named connections

A single application can talk to several MongoDB databases by giving each connection a unique connectionName. The name is then used everywhere you reference that connection, including when registering schemas with forFeature.

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost:27017/users', {
      connectionName: 'users',
    }),
    MongooseModule.forRoot('mongodb://localhost:27017/analytics', {
      connectionName: 'analytics',
    }),
  ],
})
export class AppModule {}

To inject a specific connection’s model in a feature module, pass the connection name as the second argument to forFeature, and use @InjectConnection('users') to grab the raw connection.

import { Injectable } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection } from 'mongoose';

@Injectable()
export class HealthService {
  constructor(
    @InjectConnection('users') private readonly usersConn: Connection,
  ) {}

  isHealthy(): boolean {
    // readyState 1 means connected
    return this.usersConn.readyState === 1;
  }
}

Best Practices

  • Load the connection URI from @nestjs/config with forRootAsync and use getOrThrow so a missing variable fails the boot loudly.
  • Disable autoIndex in production and manage indexes explicitly to avoid slow, repeated index builds.
  • Tune maxPoolSize and serverSelectionTimeoutMS to match your workload rather than relying on driver defaults.
  • Give every connection an explicit connectionName once you go beyond a single database, and reference that name consistently in forFeature and @InjectConnection.
  • Register a connectionFactory to log connected/disconnected events so you can observe connection health in production.
  • Keep the MongooseModule.forRoot import in the root module only; register schemas per feature with MongooseModule.forFeature.
Last updated June 14, 2026
Was this helpful?