NestJS Logo

MikroORM

MikroORM is a TypeScript ORM for Node.js based on the Data Mapper, Unit of Work, and Identity Map patterns. It supports MongoDB, MySQL, MariaDB, PostgreSQL, SQLite, Microsoft SQL Server, and Oracle. The @mikro-orm/nestjs package integrates it with Nest: it registers the ORM and its EntityManager as providers, provides repository injection, creates a request context for every HTTP request, and closes the database connection when the application shuts down.

This chapter covers the Nest-specific parts of the integration. See the MikroORM documentation for complete coverage of the ORM itself.

Note@mikro-orm/nestjs is a third-party package and is not managed by the NestJS core team. Report any issues with the library in the mikro-orm/nestjs repository.
Hint This chapter targets MikroORM v7 and @mikro-orm/nestjs v7. If you're upgrading from MikroORM v6, see the MikroORM v6 to v7 upgrading guide.

Installation#

Install the Nest module together with MikroORM, its decorators package, and the driver for your database. This chapter uses SQLite:


$ npm i @mikro-orm/nestjs @mikro-orm/core @mikro-orm/decorators @mikro-orm/sqlite

MikroORM provides a driver package for each supported database, such as @mikro-orm/postgresql, @mikro-orm/mysql, and @mikro-orm/mongodb. See the MikroORM quick start guide for the full list.

Hint Since MikroORM v7, decorators are no longer exported from @mikro-orm/core. Nest projects use legacy (experimental) decorators, so import decorators such as @Entity() and @Property() from @mikro-orm/decorators/legacy. Import EntityManager, EntityRepository, and MikroORM from your driver package (e.g., @mikro-orm/sqlite), and other types (e.g., the EntityRepositoryType symbol) from @mikro-orm/core. The snippets in this chapter include the relevant import statements.

Once the installation is complete, import the MikroOrmModule into the root AppModule:


// app.module.ts
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { ReflectMetadataProvider } from '@mikro-orm/decorators/legacy';
import { SqliteDriver } from '@mikro-orm/sqlite';

@Module({
  imports: [
    MikroOrmModule.forRoot({
      driver: SqliteDriver,
      dbName: 'my-db-name.sqlite3',
      entities: ['./dist/**/*.entity.js'],
      entitiesTs: ['./src/**/*.entity.ts'],
      metadataProvider: ReflectMetadataProvider,
    }),
  ],
})
export class AppModule {}

The forRoot() method accepts the same configuration object as MikroORM.init(). See the MikroORM configuration documentation for the complete list of options. The entities and entitiesTs glob patterns tell MikroORM where to find your entity files: it uses entitiesTs when it runs from TypeScript sources and entities when it runs from compiled JavaScript (see folder-based discovery).

Hint Since MikroORM v7, ReflectMetadataProvider is no longer the default metadata provider. Set it explicitly, as shown above, to let MikroORM infer property types from the metadata that TypeScript emits for legacy decorators.

In addition, forRoot() supports the following Nest-specific options, all described later in this chapter:

autoLoadEntitiesIf true, entities registered through forFeature() are added to the configuration automatically (default: false)
registerRequestContextIf true, registers a middleware that creates a request context for every HTTP request (default: true)
forRoutesPathThe route path that the request context middleware applies to (default: all routes)
scopeThe injection scope of the EntityManager provider (default: Scope.DEFAULT)
contextNameThe name of the connection, required when you connect to multiple databases

Alternatively, you can keep your configuration in a separate mikro-orm.config.ts file, which the MikroORM CLI uses as well. The defineConfig() helper exported by your driver package sets the driver option for you:


// mikro-orm.config.ts
import { ReflectMetadataProvider } from '@mikro-orm/decorators/legacy';
import { defineConfig } from '@mikro-orm/sqlite';

export default defineConfig({
  dbName: 'my-db-name.sqlite3',
  entities: ['./dist/**/*.entity.js'],
  entitiesTs: ['./src/**/*.entity.ts'],
  metadataProvider: ReflectMetadataProvider,
});

Then pass the configuration to forRoot(). Since MikroORM v7, forRoot() no longer accepts an empty argument list, so you must pass the configuration explicitly:


// app.module.ts
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import config from './mikro-orm.config.js';

@Module({
  imports: [MikroOrmModule.forRoot(config)],
})
export class AppModule {}

Afterward, the MikroORM and EntityManager objects are available for injection across the entire project, without importing any module elsewhere:


import { Injectable } from '@nestjs/common';
import { EntityManager, MikroORM } from '@mikro-orm/sqlite';

@Injectable()
export class AppService {
  constructor(
    private readonly orm: MikroORM,
    private readonly em: EntityManager,
  ) {}
}
Note Import EntityManager and MikroORM from the @mikro-orm/<driver> package, where <driver> is sqlite, postgresql, mysql, or whichever driver you use, to get the driver-specific API. If you have @mikro-orm/sql installed as a dependency, you can also import the EntityManager from there. The generic classes exported by @mikro-orm/core are registered as injection tokens as well.

Repositories#

MikroORM supports the repository design pattern, so each entity has its own repository. See the MikroORM repositories documentation for details. To continue the example, define a Photo entity:


// photo/photo.entity.ts
import { Entity, PrimaryKey, Property } from '@mikro-orm/decorators/legacy';

@Entity()
export class Photo {
  @PrimaryKey()
  id: number;

  @Property()
  name: string;
}
Hint Besides decorators, MikroORM can define entities with the defineEntity() helper or the EntitySchema class. Learn more in the MikroORM defining entities guide.

To define which repositories are registered in the current scope, use the forFeature() method:


// photo/photo.module.ts
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Photo } from './photo.entity.js';
import { PhotoService } from './photo.service.js';
import { PhotoController } from './photo.controller.js';

@Module({
  imports: [MikroOrmModule.forFeature([Photo])],
  providers: [PhotoService],
  controllers: [PhotoController],
})
export class PhotoModule {}
Note Don't register your base entities via forFeature(), as there are no repositories for them. Base entities do, however, need to be part of the ORM configuration (e.g., the entities option of forRoot()).

Then import the PhotoModule into the root AppModule:


// app.module.ts
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import config from './mikro-orm.config.js';
import { PhotoModule } from './photo/photo.module.js';

@Module({
  imports: [MikroOrmModule.forRoot(config), PhotoModule],
})
export class AppModule {}

You can now inject the Photo repository into the PhotoService using the @InjectRepository() decorator:


// photo/photo.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityManager, EntityRepository } from '@mikro-orm/sqlite';
import { Photo } from './photo.entity.js';

@Injectable()
export class PhotoService {
  constructor(
    @InjectRepository(Photo)
    private readonly photoRepository: EntityRepository<Photo>,
    private readonly em: EntityManager,
  ) {}

  findAll(): Promise<Photo[]> {
    return this.photoRepository.findAll();
  }

  findOne(id: number): Promise<Photo | null> {
    return this.photoRepository.findOne({ id });
  }

  async create(name: string): Promise<Photo> {
    const photo = this.photoRepository.create({ name });
    await this.em.flush();
    return photo;
  }

  async remove(id: number): Promise<void> {
    const photo = await this.photoRepository.findOneOrFail({ id });
    await this.em.remove(photo).flush();
  }
}

Repositories don't have persist() or flush() methods, so write operations go through the EntityManager, which implements the Unit of Work pattern. The repository's create() method marks the new entity for persistence, and em.flush() writes all pending changes to the database in a single transaction.

Using custom repositories#

To add your own query methods, extend EntityRepository and point the entity to your class through the repository option:


// author/author.entity.ts
import { EntityRepositoryType } from '@mikro-orm/core';
import { Entity, PrimaryKey, Property } from '@mikro-orm/decorators/legacy';
import { AuthorRepository } from './author.repository.js';

@Entity({ repository: () => AuthorRepository })
export class Author {
  // enables type inference in `em.getRepository()`
  [EntityRepositoryType]?: AuthorRepository;

  @PrimaryKey()
  id: number;

  @Property()
  name: string;
}

// author/author.repository.ts
import { EntityRepository } from '@mikro-orm/sqlite';
import { Author } from './author.entity.js';

export class AuthorRepository extends EntityRepository<Author> {
  findByName(name: string): Promise<Author | null> {
    return this.findOne({ name });
  }
}

When you pass the Author entity to forFeature(), the module also registers the custom repository class itself as a provider. You can therefore inject it by its class, without the @InjectRepository() decorator:


@Injectable()
export class AuthorService {
  constructor(private readonly authorRepository: AuthorRepository) {}
}

Load entities automatically#

Manually adding entities to the entities array of the configuration can be tedious. In addition, referencing entities from the root module breaks application domain boundaries and leaks implementation details to other parts of the application. Glob paths solve this issue, but they don't work when you bundle your application (e.g., in a monorepo): MikroORM looks up the matching files at runtime, and a bundle doesn't contain them as separate files.

As an alternative, you can load entities automatically by setting the autoLoadEntities property of the configuration object (passed into the forRoot() method) to true, as shown below:


// app.module.ts
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { ReflectMetadataProvider } from '@mikro-orm/decorators/legacy';
import { SqliteDriver } from '@mikro-orm/sqlite';

@Module({
  imports: [
    MikroOrmModule.forRoot({
      driver: SqliteDriver,
      dbName: 'my-db-name.sqlite3',
      metadataProvider: ReflectMetadataProvider,
      autoLoadEntities: true,
    }),
  ],
})
export class AppModule {}

With this option enabled, every entity registered through the forFeature() method is automatically added to the entities array of the configuration object.

Note Entities that aren't registered through the forFeature() method, but are only referenced from an entity (via a relationship), won't be included by the autoLoadEntities setting.
NoteautoLoadEntities has no effect on the MikroORM CLI, which still needs a CLI config with the full list of entities. You can use globs there, however, as the CLI doesn't run your bundle.

Request context#

MikroORM keeps loaded entities in an identity map, which must not be shared between concurrent requests. Each request needs its own fork of the EntityManager, with a clean identity map. The MikroOrmModule handles this for you: it registers a middleware for all routes that runs each HTTP request inside a RequestContext. Within that context, the global EntityManager that you inject (and every repository that depends on it) transparently delegates to the request's fork.

Outside of a request context, MikroORM refuses to use the global EntityManager for operations that depend on the identity map, such as find() or flush(), and throws a ValidationError. The next section shows how to create a context for code that doesn't run as part of an HTTP request.

To apply the middleware to a subset of routes only, set the forRoutesPath option. To disable it, set registerRequestContext to false. For example, instead of using the middleware, you can let Nest create an EntityManager fork for every request by making the EntityManager provider request-scoped:


// app.module.ts
import { Module, Scope } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import config from './mikro-orm.config.js';

@Module({
  imports: [
    MikroOrmModule.forRoot({
      ...config,
      registerRequestContext: false,
      scope: Scope.REQUEST,
    }),
  ],
})
export class AppModule {}
Warning With scope: Scope.REQUEST, every provider that depends on the EntityManager (including repositories) becomes request-scoped as well. This affects performance, as described in the injection scopes chapter.

Request scoped handlers in queues#

Middleware only runs for HTTP requests. Code outside of that flow, such as queue consumers and scheduled tasks, has no request context, so you need to create one yourself.

For these methods, use the @CreateRequestContext() decorator. It forks the EntityManager and runs the decorated async method inside a new context. The decorator looks up the EntityManager through the orm property (a MikroORM instance) or the em property (an EntityManager) of the class, so inject one of them under that name:


import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { CreateRequestContext } from '@mikro-orm/decorators/legacy';
import { MikroORM } from '@mikro-orm/sqlite';
import { Photo } from './photo.entity.js';

@Injectable()
export class PhotoStatsService {
  private readonly logger = new Logger(PhotoStatsService.name);

  constructor(private readonly orm: MikroORM) {}

  @Cron(CronExpression.EVERY_HOUR)
  @CreateRequestContext()
  async logPhotoCount() {
    // runs in a separate context, with its own identity map
    const count = await this.orm.em.count(Photo);
    this.logger.log(`Found ${count} photos`);
  }
}
Warning Place @CreateRequestContext() directly above the method, below other method decorators such as @Cron(). The decorator replaces the method with a wrapper. Decorators listed above it attach their metadata to that wrapper, but decorators listed below it attach their metadata to the original method, which Nest never sees (in this example, the cron job wouldn't be registered).
Note As the name suggests, this decorator always creates a new context, as opposed to its alternative @EnsureRequestContext(), which creates one only if the method isn't already running inside another context. Before MikroORM v6, @CreateRequestContext() was called @UseRequestContext(), and before v7, it was exported from @mikro-orm/core.

Transactions#

MikroORM queues all write operations until you call em.flush(), which then wraps them in a single transaction. To control the transaction boundaries yourself, for example, to flush several times or to combine the changes with other queries, use the em.transactional() method:


import { Injectable } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/sqlite';
import { Photo } from './photo.entity.js';

@Injectable()
export class PhotoService {
  constructor(private readonly em: EntityManager) {}

  async createMany(names: string[]): Promise<void> {
    await this.em.transactional(async (em) => {
      for (const name of names) {
        em.create(Photo, { name });
      }
    });
  }
}

The callback receives a forked EntityManager that runs inside the transaction. When the callback completes, MikroORM flushes the fork and commits the transaction. If the callback throws, MikroORM rolls the transaction back and rethrows the error.

Alternatively, use the @Transactional() decorator, which wraps the decorated async method in em.transactional():


import { Injectable } from '@nestjs/common';
import { Transactional } from '@mikro-orm/decorators/legacy';
import { EntityManager } from '@mikro-orm/sqlite';
import { Photo } from './photo.entity.js';

@Injectable()
export class PhotoService {
  constructor(private readonly em: EntityManager) {}

  @Transactional()
  async createMany(names: string[]): Promise<void> {
    for (const name of names) {
      this.em.create(Photo, { name });
    }
  }
}

Like @CreateRequestContext(), the decorator finds the EntityManager through the em or orm property of the class, and falls back to the current context if the class has neither. When called inside another transaction, em.transactional() starts a nested transaction (a savepoint), while @Transactional() joins the existing transaction by default. See the MikroORM transactions documentation for propagation options, isolation levels, and locking.

Serialization#

MikroORM entities have a toJSON() method that applies MikroORM's serialization rules: it omits hidden properties, applies custom property serializers, and serializes populated relations as nested objects and unpopulated relations as their primary keys. When a route handler returns an entity, Nest converts it to JSON, so these rules apply automatically.

Warning Don't use Nest's built-in serializer (the ClassSerializerInterceptor) with MikroORM entities. It relies on class-transformer, which doesn't understand the wrappers that MikroORM uses for relations: it serializes the internal structure of a Reference, and traversing a Collection leads to infinite recursion (a "Maximum call stack size exceeded" error).

Instead, configure serialization on the entity with MikroORM's options:


// book/book.entity.ts
import { type Rel } from '@mikro-orm/core';
import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/decorators/legacy';
import { Author } from '../author/author.entity.js';

@Entity()
export class Book {
  @PrimaryKey()
  id: number;

  @Property()
  title: string;

  @Property({ hidden: true }) // Equivalent of class-transformer's `@Exclude()`
  hiddenField: number = Date.now();

  @Property({ persist: false }) // Similar to class-transformer's `@Expose()`. Only exists in memory, but is serialized.
  count?: number;

  @ManyToOne(() => Author, {
    serializer: (author: Author) => author.name,
    serializedName: 'authorName',
  }) // Equivalent of class-transformer's `@Transform()`
  author: Rel<Author>;
}
Hint When entities reference each other, wrap the relation's type in Rel<T> (or use Ref<T>) from @mikro-orm/core, and pass the target entity to the relation decorator, as shown above. Otherwise, TypeScript emits decorator metadata that references the related class directly. In ESM projects, the circular imports between entity files then fail at startup with a ReferenceError (e.g., "Cannot access 'Author' before initialization").

To control serialization for a single route handler, convert the entity explicitly with the serialize() function, which is exported by your driver package and by @mikro-orm/core. It accepts options such as populate, exclude, fields, and groups:


@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
  const book = await this.em.findOneOrFail(Book, { id }, { populate: ['author'] });
  return serialize(book, { fields: ['title', 'author'] }); // { title: '...', authorName: '...' }
}

To get a plain object with the default rules applied, call wrap(entity).toObject().

Multiple databases#

To connect to multiple databases, call MikroOrmModule.forRoot() once per database, and give each connection a unique contextName. In this setup, set registerRequestContext to false on every connection, because the default middleware only works with a single, unnamed connection. Instead, import MikroOrmModule.forMiddleware() once. Its middleware creates a request context that covers all connections:


// app.module.ts
import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { ReflectMetadataProvider } from '@mikro-orm/decorators/legacy';
import { SqliteDriver } from '@mikro-orm/sqlite';
import { Album } from './album/album.entity.js';
import { Photo } from './photo/photo.entity.js';

@Module({
  imports: [
    MikroOrmModule.forRoot({
      contextName: 'photos',
      registerRequestContext: false,
      driver: SqliteDriver,
      dbName: 'photos.sqlite3',
      entities: [Photo],
      metadataProvider: ReflectMetadataProvider,
    }),
    MikroOrmModule.forRoot({
      contextName: 'albums',
      registerRequestContext: false,
      driver: SqliteDriver,
      dbName: 'albums.sqlite3',
      entities: [Album],
      metadataProvider: ReflectMetadataProvider,
    }),
    MikroOrmModule.forMiddleware(),
  ],
})
export class AppModule {}
Notice If you use MikroOrmModule.forRootAsync(), set the contextName next to useFactory, not inside the options object that the factory returns.

With this setup, pass the context name to the forFeature() method and the @InjectRepository() decorator:


// album/album.module.ts
@Module({
  imports: [MikroOrmModule.forFeature([Album], 'albums')],
  providers: [AlbumService],
})
export class AlbumModule {}

To inject the MikroORM or EntityManager of a specific connection, use the @InjectMikroORM() and @InjectEntityManager() decorators:


// album/album.service.ts
import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectMikroORM, InjectRepository } from '@mikro-orm/nestjs';
import { EntityManager, EntityRepository, MikroORM } from '@mikro-orm/sqlite';
import { Album } from './album.entity.js';

@Injectable()
export class AlbumService {
  constructor(
    @InjectRepository(Album, 'albums')
    private readonly albumRepository: EntityRepository<Album>,
    @InjectEntityManager('albums')
    private readonly em: EntityManager,
    @InjectMikroORM('albums')
    private readonly orm: MikroORM,
  ) {}
}

To inject all registered MikroORM instances as an array, use the @InjectMikroORMs() decorator.

Testing#

When unit testing an application, you usually want to avoid a database connection, which keeps test suites independent and fast. However, your classes might depend on repositories and the EntityManager. To resolve these classes, provide mock implementations using custom providers.

The @mikro-orm/nestjs package exposes a getRepositoryToken() function that returns a prepared injection token based on a given entity (e.g., 'PhotoRepository' for the Photo entity), allowing you to mock the repository. For a custom repository, use its class as the token:


import { Module } from '@nestjs/common';
import { getRepositoryToken } from '@mikro-orm/nestjs';
import { EntityManager } from '@mikro-orm/sqlite';
import { Photo } from './photo.entity.js';
import { PhotoService } from './photo.service.js';

@Module({
  providers: [
    PhotoService,
    {
      // or, for a custom repository: `provide: PhotoRepository`
      provide: getRepositoryToken(Photo),
      useValue: mockedRepository,
    },
    {
      provide: EntityManager,
      useValue: mockedEntityManager,
    },
  ],
})
export class PhotoModule {}

Now the mockedRepository object is injected wherever a class in this module asks for the Photo repository using the @InjectRepository(Photo) decorator. For multiple databases, pass the context name as the second argument, e.g., getRepositoryToken(Album, 'albums').

Async configuration#

When you need to pass module options asynchronously instead of statically, use the forRootAsync() method. As with most dynamic modules, Nest provides several techniques for async configuration.

One technique is to use a factory function. Like other factory providers, the factory function can be async and can inject dependencies through inject:


MikroOrmModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    dbName: configService.getOrThrow<string>('DB_NAME'),
    autoLoadEntities: true,
    metadataProvider: ReflectMetadataProvider,
  }),
  inject: [ConfigService],
  driver: SqliteDriver,
});
Warning Set the driver option next to useFactory, as shown above. The module needs to know the driver before it calls the factory, to register the driver-specific EntityManager and MikroORM classes as injection tokens. Without it, injecting the EntityManager from your driver package fails with a dependency resolution error.

Alternatively, you can configure the MikroOrmModule using a class instead of a factory, as shown below:


MikroOrmModule.forRootAsync({
  useClass: MikroOrmConfigService,
  driver: SqliteDriver,
});

The construction above instantiates MikroOrmConfigService inside MikroOrmModule, using it to create the required options object. In this example, the MikroOrmConfigService has to implement the MikroOrmOptionsFactory interface, as shown below. The MikroOrmModule calls the createMikroOrmOptions() method on the instantiated object of the supplied class.


import { Injectable } from '@nestjs/common';
import { MikroOrmModuleOptions, MikroOrmOptionsFactory } from '@mikro-orm/nestjs';
import { ReflectMetadataProvider } from '@mikro-orm/decorators/legacy';

@Injectable()
export class MikroOrmConfigService implements MikroOrmOptionsFactory {
  createMikroOrmOptions(): MikroOrmModuleOptions {
    return {
      dbName: 'my-db-name.sqlite3',
      autoLoadEntities: true,
      metadataProvider: ReflectMetadataProvider,
    };
  }
}

To reuse an existing options provider instead of creating a private copy inside the MikroOrmModule, use the useExisting syntax. The provider must implement the MikroOrmOptionsFactory interface, and a module listed in imports must export it:


MikroOrmModule.forRootAsync({
  imports: [DatabaseConfigModule],
  useExisting: MikroOrmConfigService,
  driver: SqliteDriver,
});

Application shutdown#

The MikroOrmModule closes the ORM and its database connection in the onApplicationShutdown() lifecycle hook, which Nest calls when you call app.close(). By default, however, Nest doesn't listen for process termination signals such as SIGTERM, so the connection isn't closed when the process is terminated. To close it in that case too, enable shutdown hooks:


// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks();
  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();

Example#

For complete applications that use NestJS with MikroORM, see the nestjs-realworld-example-app and nestjs-example-app repositories.

Edit on GitHub

Support us

Nest is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you'd like to join them, please read more here.

Principal Sponsors

SerpApi LogoTrilon LogoMojam Logo

Sponsors / Partners

Become a sponsor