Database

Nest is database agnostic, allowing you to easily integrate with any SQL or NoSQL database. You have a number of options available to you, depending on your preferences. At the most general level, connecting Nest to a database is simply a matter of loading an appropriate Node.js driver for the database, just as you would with Express or Fastify.

You can also directly use any general purpose Node.js database integration library or ORM, such as Sequelize (navigate to the Sequelize integration section), Knex.js (tutorial) and TypeORM, to operate at a higher level of abstraction.

For convenience, Nest provides tight integration with TypeORM and Sequelize out-of-the-box with the @nestjs/typeorm and @nestjs/sequelize packages respectively, which we’ll cover in the current chapter, and Mongoose with @nestjs/mongoose, which is covered in this chapter. These integrations provide additional NestJS-specific features, such as model/repository injection, testability, and asynchronous configuration to make accessing your chosen database even easier.

TypeORM Integration

For integrating with SQL and NoSQL databases, Nest provides the @nestjs/typeorm package. Nest uses TypeORM because it’s the most mature Object Relational Mapper (ORM) available for TypeScript. Since it’s written in TypeScript, it integrates well with the Nest framework.

To begin using it, we first install the required dependencies. In this chapter, we’ll demonstrate using the popular MySQL Relational DBMS, but TypeORM provides support for many relational databases, such as PostgreSQL, Oracle, Microsoft SQL Server, SQLite, and even NoSQL databases like MongoDB. The procedure we walk through in this chapter will be the same for any database supported by TypeORM. You’ll simply need to install the associated client API libraries for your selected database.

  1. $ npm install --save @nestjs/typeorm typeorm mysql

Once the installation process is complete, we can import the TypeOrmModule into the root AppModule.

app.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { TypeOrmModule } from '@nestjs/typeorm';
  3. @Module({
  4. imports: [
  5. TypeOrmModule.forRoot({
  6. type: 'mysql',
  7. host: 'localhost',
  8. port: 3306,
  9. username: 'root',
  10. password: 'root',
  11. database: 'test',
  12. entities: [],
  13. synchronize: true,
  14. }),
  15. ],
  16. })
  17. export class AppModule {}

The forRoot() method supports all the configuration properties exposed by the createConnection() function from the TypeORM package. In addition, there are several extra configuration properties described below.

retryAttemptsNumber of attempts to connect to the database (default: 10)
retryDelayDelay between connection retry attempts (ms) (default: 3000)
autoLoadEntitiesIf true, entities will be loaded automatically (default: false)
keepConnectionAliveIf true, connection will not be closed on application shutdown (default: false)

Hint Learn more about the connection options here.

Alternatively, rather than passing a configuration object to forRoot(), we can create an ormconfig.json file in the project root directory.

  1. {
  2. "type": "mysql",
  3. "host": "localhost",
  4. "port": 3306,
  5. "username": "root",
  6. "password": "root",
  7. "database": "test",
  8. "entities": ["dist/**/*.entity{.ts,.js}"],
  9. "synchronize": true
  10. }

Then, we can call forRoot() without any options:

app.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { TypeOrmModule } from '@nestjs/typeorm';
  3. @Module({
  4. imports: [TypeOrmModule.forRoot()],
  5. })
  6. export class AppModule {}

Warning Static glob paths (e.g., dist/**/*.entity{ .ts,.js}) won’t work properly with webpack.

Warning Note that the ormconfig.json file is loaded by the typeorm library. Thus, any of the extra properties described above (which are supported internally by way of the forRoot() method - for example, autoLoadEntities and retryDelay) won’t be applied.

Once this is done, the TypeORM Connection and EntityManager objects will be available to inject across the entire project (without needing to import any modules), for example:

app.module.ts

  1. import { Connection } from 'typeorm';
  2. @Module({
  3. imports: [TypeOrmModule.forRoot(), UsersModule],
  4. })
  5. export class AppModule {
  6. constructor(private connection: Connection) {}
  7. }
  1. import { Connection } from 'typeorm';
  2. @Dependencies(Connection)
  3. @Module({
  4. imports: [TypeOrmModule.forRoot(), UsersModule],
  5. })
  6. export class AppModule {
  7. constructor(connection) {
  8. this.connection = connection;
  9. }
  10. }

Repository pattern

TypeORM supports the repository design pattern, so each entity has its own repository. These repositories can be obtained from the database connection.

To continue the example, we need at least one entity. Let’s define the User entity.

user.entity.ts

  1. import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
  2. @Entity()
  3. export class User {
  4. @PrimaryGeneratedColumn()
  5. id: number;
  6. @Column()
  7. firstName: string;
  8. @Column()
  9. lastName: string;
  10. @Column({ default: true })
  11. isActive: boolean;
  12. }

Hint Learn more about entities in the TypeORM documentation.

The User entity file sits in the users directory. This directory contains all files related to the UsersModule. You can decide where to keep your model files, however, we recommend creating them near their domain, in the corresponding module directory.

To begin using the User entity, we need to let TypeORM know about it by inserting it into the entities array in the module forRoot() method options (unless you use a static glob path):

app.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { TypeOrmModule } from '@nestjs/typeorm';
  3. import { User } from './users/user.entity';
  4. @Module({
  5. imports: [
  6. TypeOrmModule.forRoot({
  7. type: 'mysql',
  8. host: 'localhost',
  9. port: 3306,
  10. username: 'root',
  11. password: 'root',
  12. database: 'test',
  13. entities: [User],
  14. synchronize: true,
  15. }),
  16. ],
  17. })
  18. export class AppModule {}

Next, let’s look at the UsersModule:

users.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { TypeOrmModule } from '@nestjs/typeorm';
  3. import { UsersService } from './users.service';
  4. import { UsersController } from './users.controller';
  5. import { User } from './user.entity';
  6. @Module({
  7. imports: [TypeOrmModule.forFeature([User])],
  8. providers: [UsersService],
  9. controllers: [UsersController],
  10. })
  11. export class UsersModule {}

This module uses the forFeature() method to define which repositories are registered in the current scope. With that in place, we can inject the UsersRepository into the UsersService using the @InjectRepository() decorator:

users.service.ts

  1. import { Injectable } from '@nestjs/common';
  2. import { InjectRepository } from '@nestjs/typeorm';
  3. import { Repository } from 'typeorm';
  4. import { User } from './user.entity';
  5. @Injectable()
  6. export class UsersService {
  7. constructor(
  8. @InjectRepository(User)
  9. private usersRepository: Repository<User>,
  10. ) {}
  11. findAll(): Promise<User[]> {
  12. return this.usersRepository.find();
  13. }
  14. findOne(id: string): Promise<User> {
  15. return this.usersRepository.findOne(id);
  16. }
  17. async remove(id: string): Promise<void> {
  18. await this.usersRepository.delete(id);
  19. }
  20. }
  1. import { Injectable, Dependencies } from '@nestjs/common';
  2. import { getRepositoryToken } from '@nestjs/typeorm';
  3. import { User } from './user.entity';
  4. @Injectable()
  5. @Dependencies(getRepositoryToken(User))
  6. export class UsersService {
  7. constructor(usersRepository) {
  8. this.usersRepository = usersRepository;
  9. }
  10. findAll() {
  11. return this.usersRepository.find();
  12. }
  13. findOne(id) {
  14. return this.usersRepository.findOne(id);
  15. }
  16. async remove(id) {
  17. await this.usersRepository.delete(id);
  18. }
  19. }

Notice Don’t forget to import the UsersModule into the root AppModule.

If you want to use the repository outside of the module which imports TypeOrmModule.forFeature, you’ll need to re-export the providers generated by it. You can do this by exporting the whole module, like this:

users.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { TypeOrmModule } from '@nestjs/typeorm';
  3. import { User } from './user.entity';
  4. @Module({
  5. imports: [TypeOrmModule.forFeature([User])],
  6. exports: [TypeOrmModule]
  7. })
  8. export class UsersModule {}

Now if we import UsersModule in UserHttpModule, we can use @InjectRepository(User) in the providers of the latter module.

users-http.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { UsersModule } from './user.module';
  3. import { UsersService } from './users.service';
  4. import { UsersController } from './users.controller';
  5. @Module({
  6. imports: [UsersModule],
  7. providers: [UsersService],
  8. controllers: [UsersController]
  9. })
  10. export class UserHttpModule {}

Relations

Relations are associations established between two or more tables. Relations are based on common fields from each table, often involving primary and foreign keys.

There are three types of relations:

One-to-oneEvery row in the primary table has one and only one associated row in the foreign table. Use the @OneToOne() decorator to define this type of relation.
One-to-many / Many-to-oneEvery row in the primary table has one or more related rows in the foreign table. Use the @OneToMany() and @ManyToOne() decorators to define this type of relation.
Many-to-manyEvery row in the primary table has many related rows in the foreign table, and every record in the foreign table has many related rows in the primary table. Use the @ManyToMany() decorator to define this type of relation.

To define relations in entities, use the corresponding decorators. For example, to define that each User can have multiple photos, use the @OneToMany() decorator.

user.entity.ts

  1. import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
  2. import { Photo } from '../photos/photo.entity';
  3. @Entity()
  4. export class User {
  5. @PrimaryGeneratedColumn()
  6. id: number;
  7. @Column()
  8. firstName: string;
  9. @Column()
  10. lastName: string;
  11. @Column({ default: true })
  12. isActive: boolean;
  13. @OneToMany(type => Photo, photo => photo.user)
  14. photos: Photo[];
  15. }

Hint To learn more about relations in TypeORM, visit the TypeORM documentation.

Auto-load entities

Manually adding entities to the entities array of the connection options can be tedious. In addition, referencing entities from the root module breaks application domain boundaries and causes leaking implementation details to other parts of the application. To solve this issue, static glob paths can be used (e.g., dist/**/*.entity{ .ts,.js}).

Note, however, that glob paths are not supported by webpack, so if you are building your application within a monorepo, you won’t be able to use them. To address this issue, an alternative solution is provided. To automatically load entities, set the autoLoadEntities property of the configuration object (passed into the forRoot() method) to true, as shown below:

app.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { TypeOrmModule } from '@nestjs/typeorm';
  3. @Module({
  4. imports: [
  5. TypeOrmModule.forRoot({
  6. ...
  7. autoLoadEntities: true,
  8. }),
  9. ],
  10. })
  11. export class AppModule {}

With that option specified, every entity registered through the forFeature() method will be automatically added to the entities array of the configuration object.

Warning Note that entities that aren’t registered through the forFeature() method, but are only referenced from the entity (via a relationship), won’t be included by way of the autoLoadEntities setting.

Transactions

A database transaction symbolizes a unit of work performed within a database management system against a database, and treated in a coherent and reliable way independent of other transactions. A transaction generally represents any change in a database (learn more).

There are many different strategies to handle TypeORM transactions. We recommend using the QueryRunner class because it gives full control over the transaction.

First, we need to inject the Connection object into a class in the normal way:

  1. @Injectable()
  2. export class UsersService {
  3. constructor(private connection: Connection) {}
  4. }

Hint The Connection class is imported from the typeorm package.

Now, we can use this object to create a transaction.

  1. async createMany(users: User[]) {
  2. const queryRunner = this.connection.createQueryRunner();
  3. await queryRunner.connect();
  4. await queryRunner.startTransaction();
  5. try {
  6. await queryRunner.manager.save(users[0]);
  7. await queryRunner.manager.save(users[1]);
  8. await queryRunner.commitTransaction();
  9. } catch (err) {
  10. // since we have errors lets rollback the changes we made
  11. await queryRunner.rollbackTransaction();
  12. } finally {
  13. // you need to release a queryRunner which was manually instantiated
  14. await queryRunner.release();
  15. }
  16. }

Hint Note that the connection is used only to create the QueryRunner. However, to test this class would require mocking the entire Connection object (which exposes several methods). Thus, we recommend using a helper factory class (e.g., QueryRunnerFactory) and defining an interface with a limited set of methods required to maintain transactions. This technique makes mocking these methods pretty straightforward.

Alternatively, you can use the callback-style approach with the transaction method of the Connection object (read more).

  1. async createMany(users: User[]) {
  2. await this.connection.transaction(async manager => {
  3. await manager.save(users[0]);
  4. await manager.save(users[1]);
  5. });
  6. }

Using decorators to control the transaction (@Transaction() and @TransactionManager()) is not recommended.

Hoodies, T-shirts, and accessories!

Support our future development by shopping in the official store!

See more

Subscribers

With TypeORM subscribers, you can listen to specific entity events.

  1. import {
  2. Connection,
  3. EntitySubscriberInterface,
  4. EventSubscriber,
  5. InsertEvent,
  6. } from 'typeorm';
  7. import { User } from './user.entity';
  8. @EventSubscriber()
  9. export class UserSubscriber implements EntitySubscriberInterface<User> {
  10. constructor(connection: Connection) {
  11. connection.subscribers.push(this);
  12. }
  13. listenTo() {
  14. return User;
  15. }
  16. beforeInsert(event: InsertEvent<User>) {
  17. console.log(`BEFORE USER INSERTED: `, event.entity);
  18. }
  19. }

Warning Event subscribers can not be request-scoped.

Now, add the UserSubscriber class to the providers array:

  1. import { Module } from '@nestjs/common';
  2. import { TypeOrmModule } from '@nestjs/typeorm';
  3. import { User } from './user.entity';
  4. import { UsersController } from './users.controller';
  5. import { UsersService } from './users.service';
  6. import { UserSubscriber } from './user.subscriber';
  7. @Module({
  8. imports: [TypeOrmModule.forFeature([User])],
  9. providers: [UsersService, UserSubscriber],
  10. controllers: [UsersController],
  11. })
  12. export class UsersModule {}

Hint Learn more about entity subscribers here.

Migrations

Migrations provide a way to incrementally update the database schema to keep it in sync with the application’s data model while preserving existing data in the database. To generate, run, and revert migrations, TypeORM provides a dedicated CLI.

Migration classes are separate from the Nest application source code. Their lifecycle is maintained by the TypeORM CLI. Therefore, you are not able to leverage dependency injection and other Nest specific features with migrations. To learn more about migrations, follow the guide in the TypeORM documentation.

Multiple databases

Some projects require multiple database connections. This can also be achieved with this module. To work with multiple connections, first create the connections. In this case, connection naming becomes mandatory.

Suppose you have an Album entity stored in its own database.

  1. const defaultOptions = {
  2. type: 'postgres',
  3. port: 5432,
  4. username: 'user',
  5. password: 'password',
  6. database: 'db',
  7. synchronize: true,
  8. };
  9. @Module({
  10. imports: [
  11. TypeOrmModule.forRoot({
  12. ...defaultOptions,
  13. host: 'user_db_host',
  14. entities: [User],
  15. }),
  16. TypeOrmModule.forRoot({
  17. ...defaultOptions,
  18. name: 'albumsConnection',
  19. host: 'album_db_host',
  20. entities: [Album],
  21. }),
  22. ],
  23. })
  24. export class AppModule {}

Notice If you don’t set the name for a connection, its name is set to default. Please note that you shouldn’t have multiple connections without a name, or with the same name, otherwise they will get overridden.

At this point, you have User and Album entities registered with their own connection. With this setup, you have to tell the TypeOrmModule.forFeature() method and the @InjectRepository() decorator which connection should be used. If you do not pass any connection name, the default connection is used.

  1. @Module({
  2. imports: [
  3. TypeOrmModule.forFeature([User]),
  4. TypeOrmModule.forFeature([Album], 'albumsConnection'),
  5. ],
  6. })
  7. export class AppModule {}

You can also inject the Connection or EntityManager for a given connection:

  1. @Injectable()
  2. export class AlbumsService {
  3. constructor(
  4. @InjectConnection('albumsConnection')
  5. private connection: Connection,
  6. @InjectEntityManager('albumsConnection')
  7. private entityManager: EntityManager,
  8. ) {}
  9. }

Testing

When it comes to unit testing an application, we usually want to avoid making a database connection, keeping our test suites independent and their execution process as fast as possible. But our classes might depend on repositories that are pulled from the connection instance. How do we handle that? The solution is to create mock repositories. In order to achieve that, we set up custom providers. Each registered repository is automatically represented by an <EntityName>Repository token, where EntityName is the name of your entity class.

The @nestjs/typeorm package exposes the getRepositoryToken() function which returns a prepared token based on a given entity.

  1. @Module({
  2. providers: [
  3. UsersService,
  4. {
  5. provide: getRepositoryToken(User),
  6. useValue: mockRepository,
  7. },
  8. ],
  9. })
  10. export class UsersModule {}

Now a substitute mockRepository will be used as the UsersRepository. Whenever any class asks for UsersRepository using an @InjectRepository() decorator, Nest will use the registered mockRepository object.

Custom repository

TypeORM provides a feature called custom repositories. Custom repositories allow you to extend a base repository class, and enrich it with several special methods. To learn more about this feature, visit this page.

In order to create your custom repository, use the @EntityRepository() decorator and extend the Repository class.

  1. @EntityRepository(Author)
  2. export class AuthorRepository extends Repository<Author> {}

Hint Both @EntityRepository() and Repository are imported from the typeorm package.

Once the class is created, the next step is to delegate instantiation responsibility to Nest. For this, we have to pass theAuthorRepository class to the TypeOrm.forFeature() method.

  1. @Module({
  2. imports: [TypeOrmModule.forFeature([AuthorRepository])],
  3. controller: [AuthorController],
  4. providers: [AuthorService],
  5. })
  6. export class AuthorModule {}

Afterward, simply inject the repository using the following construction:

  1. @Injectable()
  2. export class AuthorService {
  3. constructor(private authorRepository: AuthorRepository) {}
  4. }

Async configuration

You may want to pass your repository module options asynchronously instead of statically. In this case, use the forRootAsync() method, which provides several ways to deal with async configuration.

One approach is to use a factory function:

  1. TypeOrmModule.forRootAsync({
  2. useFactory: () => ({
  3. type: 'mysql',
  4. host: 'localhost',
  5. port: 3306,
  6. username: 'root',
  7. password: 'root',
  8. database: 'test',
  9. entities: [__dirname + '/**/*.entity{.ts,.js}'],
  10. synchronize: true,
  11. }),
  12. });

Our factory behaves like any other asynchronous provider (e.g., it can be async and it’s able to inject dependencies through inject).

  1. TypeOrmModule.forRootAsync({
  2. imports: [ConfigModule],
  3. useFactory: (configService: ConfigService) => ({
  4. type: 'mysql',
  5. host: configService.get('HOST'),
  6. port: +configService.get<number>('PORT'),
  7. username: configService.get('USERNAME'),
  8. password: configService.get('PASSWORD'),
  9. database: configService.get('DATABASE'),
  10. entities: [__dirname + '/**/*.entity{.ts,.js}'],
  11. synchronize: true,
  12. }),
  13. inject: [ConfigService],
  14. });

Alternatively, you can use the useClass syntax:

  1. TypeOrmModule.forRootAsync({
  2. useClass: TypeOrmConfigService,
  3. });

The construction above will instantiate TypeOrmConfigService inside TypeOrmModule and use it to provide an options object by calling createTypeOrmOptions(). Note that this means that the TypeOrmConfigService has to implement the TypeOrmOptionsFactory interface, as shown below:

  1. @Injectable()
  2. class TypeOrmConfigService implements TypeOrmOptionsFactory {
  3. createTypeOrmOptions(): TypeOrmModuleOptions {
  4. return {
  5. type: 'mysql',
  6. host: 'localhost',
  7. port: 3306,
  8. username: 'root',
  9. password: 'root',
  10. database: 'test',
  11. entities: [__dirname + '/**/*.entity{.ts,.js}'],
  12. synchronize: true,
  13. };
  14. }
  15. }

In order to prevent the creation of TypeOrmConfigService inside TypeOrmModule and use a provider imported from a different module, you can use the useExisting syntax.

  1. TypeOrmModule.forRootAsync({
  2. imports: [ConfigModule],
  3. useExisting: ConfigService,
  4. });

This construction works the same as useClass with one critical difference - TypeOrmModule will lookup imported modules to reuse an existing ConfigService instead of instantiating a new one.

Example

A working example is available here.

Official enterprise support

  • Database - 图1 Providing technical guidance
  • Database - 图2 Performing in-depth code reviews
  • Database - 图3 Mentoring team members
  • Database - 图4 Advising best practices

Explore more

Sequelize Integration

An alternative to using TypeORM is to use the Sequelize ORM with the @nestjs/sequelize package. In addition, we leverage the sequelize-typescript package which provides a set of additional decorators to declaratively define entities.

To begin using it, we first install the required dependencies. In this chapter, we’ll demonstrate using the popular MySQL Relational DBMS, but Sequelize provides support for many relational databases, such as PostgreSQL, MySQL, Microsoft SQL Server, SQLite, and MariaDB. The procedure we walk through in this chapter will be the same for any database supported by Sequelize. You’ll simply need to install the associated client API libraries for your selected database.

  1. $ npm install --save @nestjs/sequelize sequelize sequelize-typescript mysql2
  2. $ npm install --save-dev @types/sequelize

Once the installation process is complete, we can import the SequelizeModule into the root AppModule.

app.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { SequelizeModule } from '@nestjs/sequelize';
  3. @Module({
  4. imports: [
  5. SequelizeModule.forRoot({
  6. dialect: 'mysql',
  7. host: 'localhost',
  8. port: 3306,
  9. username: 'root',
  10. password: 'root',
  11. database: 'test',
  12. models: [],
  13. }),
  14. ],
  15. })
  16. export class AppModule {}

The forRoot() method supports all the configuration properties exposed by the Sequelize constructor (read more). In addition, there are several extra configuration properties described below.

retryAttemptsNumber of attempts to connect to the database (default: 10)
retryDelayDelay between connection retry attempts (ms) (default: 3000)
autoLoadModelsIf true, models will be loaded automatically (default: false)
keepConnectionAliveIf true, connection will not be closed on the application shutdown (default: false)
synchronizeIf true, automatically loaded models will be synchronized (default: false)

Once this is done, the Sequelize object will be available to inject across the entire project (without needing to import any modules), for example:

app.service.ts

  1. import { Injectable } from '@nestjs/common';
  2. import { Sequelize } from 'sequelize-typescript';
  3. @Injectable()
  4. export class AppService {
  5. constructor(private sequelize: Sequelize) {}
  6. }
  1. import { Injectable } from '@nestjs/common';
  2. import { Sequelize } from 'sequelize-typescript';
  3. @Dependencies(Sequelize)
  4. @Injectable()
  5. export class AppService {
  6. constructor(sequelize) {
  7. this.sequelize = sequelize;
  8. }
  9. }

Models

Sequelize implements the Active Record pattern. With this pattern, you use model classes directly to interact with the database. To continue the example, we need at least one model. Let’s define the User model.

user.model.ts

  1. import { Column, Model, Table } from 'sequelize-typescript';
  2. @Table
  3. export class User extends Model<User> {
  4. @Column
  5. firstName: string;
  6. @Column
  7. lastName: string;
  8. @Column({ defaultValue: true })
  9. isActive: boolean;
  10. }

Hint Learn more about the available decorators here.

The User model file sits in the users directory. This directory contains all files related to the UsersModule. You can decide where to keep your model files, however, we recommend creating them near their domain, in the corresponding module directory.

To begin using the User model, we need to let Sequelize know about it by inserting it into the models array in the module forRoot() method options:

app.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { SequelizeModule } from '@nestjs/sequelize';
  3. import { User } from './users/user.model';
  4. @Module({
  5. imports: [
  6. SequelizeModule.forRoot({
  7. dialect: 'mysql',
  8. host: 'localhost',
  9. port: 3306,
  10. username: 'root',
  11. password: 'root',
  12. database: 'test',
  13. models: [User],
  14. }),
  15. ],
  16. })
  17. export class AppModule {}

Next, let’s look at the UsersModule:

users.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { SequelizeModule } from '@nestjs/sequelize';
  3. import { User } from './user.model';
  4. import { UsersController } from './users.controller';
  5. import { UsersService } from './users.service';
  6. @Module({
  7. imports: [SequelizeModule.forFeature([User])],
  8. providers: [UsersService],
  9. controllers: [UsersController],
  10. })
  11. export class UsersModule {}

This module uses the forFeature() method to define which models are registered in the current scope. With that in place, we can inject the UserModel into the UsersService using the @InjectModel() decorator:

users.service.ts

  1. import { Injectable } from '@nestjs/common';
  2. import { InjectModel } from '@nestjs/sequelize';
  3. import { User } from './user.model';
  4. @Injectable()
  5. export class UsersService {
  6. constructor(
  7. @InjectModel(User)
  8. private userModel: typeof User,
  9. ) {}
  10. async findAll(): Promise<User[]> {
  11. return this.userModel.findAll();
  12. }
  13. findOne(id: string): Promise<User> {
  14. return this.userModel.findOne({
  15. where: {
  16. id,
  17. },
  18. });
  19. }
  20. async remove(id: string): Promise<void> {
  21. const user = await this.findOne(id);
  22. await user.destroy();
  23. }
  24. }
  1. import { Injectable, Dependencies } from '@nestjs/common';
  2. import { getModelToken } from '@nestjs/sequelize';
  3. import { User } from './user.model';
  4. @Injectable()
  5. @Dependencies(getModelToken(User))
  6. export class UsersService {
  7. constructor(usersRepository) {
  8. this.usersRepository = usersRepository;
  9. }
  10. async findAll() {
  11. return this.userModel.findAll();
  12. }
  13. findOne(id) {
  14. return this.userModel.findOne({
  15. where: {
  16. id,
  17. },
  18. });
  19. }
  20. async remove(id) {
  21. const user = await this.findOne(id);
  22. await user.destroy();
  23. }
  24. }

Notice Don’t forget to import the UsersModule into the root AppModule.

If you want to use the repository outside of the module which imports SequelizeModule.forFeature, you’ll need to re-export the providers generated by it. You can do this by exporting the whole module, like this:

users.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { SequelizeModule } from '@nestjs/sequelize';
  3. import { User } from './user.entity';
  4. @Module({
  5. imports: [SequelizeModule.forFeature([User])],
  6. exports: [SequelizeModule]
  7. })
  8. export class UsersModule {}

Now if we import UsersModule in UserHttpModule, we can use @InjectModel(User) in the providers of the latter module.

users-http.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { UsersModule } from './user.module';
  3. import { UsersService } from './users.service';
  4. import { UsersController } from './users.controller';
  5. @Module({
  6. imports: [UsersModule],
  7. providers: [UsersService],
  8. controllers: [UsersController]
  9. })
  10. export class UserHttpModule {}

Relations

Relations are associations established between two or more tables. Relations are based on common fields from each table, often involving primary and foreign keys.

There are three types of relations:

One-to-oneEvery row in the primary table has one and only one associated row in the foreign table
One-to-many / Many-to-oneEvery row in the primary table has one or more related rows in the foreign table
Many-to-manyEvery row in the primary table has many related rows in the foreign table, and every record in the foreign table has many related rows in the primary table

To define relations in entities, use the corresponding decorators. For example, to define that each User can have multiple photos, use the @HasMany() decorator.

user.entity.ts

  1. import { Column, Model, Table, HasMany } from 'sequelize-typescript';
  2. import { Photo } from '../photos/photo.model';
  3. @Table
  4. export class User extends Model<User> {
  5. @Column
  6. firstName: string;
  7. @Column
  8. lastName: string;
  9. @Column({ defaultValue: true })
  10. isActive: boolean;
  11. @HasMany(() => Photo)
  12. photos: Photo[];
  13. }

Hint To learn more about associations in Sequelize, read this chapter.

Auto-load models

Manually adding models to the models array of the connection options can be tedious. In addition, referencing models from the root module breaks application domain boundaries and causes leaking implementation details to other parts of the application. To solve this issue, automatically load models by setting both autoLoadModels and synchronize properties of the configuration object (passed into the forRoot() method) to true, as shown below:

app.module.ts

  1. import { Module } from '@nestjs/common';
  2. import { SequelizeModule } from '@nestjs/sequelize';
  3. @Module({
  4. imports: [
  5. SequelizeModule.forRoot({
  6. ...
  7. autoLoadModels: true,
  8. synchronize: true,
  9. }),
  10. ],
  11. })
  12. export class AppModule {}

With that option specified, every model registered through the forFeature() method will be automatically added to the models array of the configuration object.

Warning Note that models that aren’t registered through the forFeature() method, but are only referenced from the model (via an association), won’t be included.

Transactions

A database transaction symbolizes a unit of work performed within a database management system against a database, and treated in a coherent and reliable way independent of other transactions. A transaction generally represents any change in a database (learn more).

There are many different strategies to handle Sequelize transactions. Below is a sample implementation of a managed transaction (auto-callback).

First, we need to inject the Sequelize object into a class in the normal way:

  1. @Injectable()
  2. export class UsersService {
  3. constructor(private sequelize: Sequelize) {}
  4. }

Hint The Sequelize class is imported from the sequelize-typescript package.

Now, we can use this object to create a transaction.

  1. async createMany() {
  2. try {
  3. await this.sequelize.transaction(async t => {
  4. const transactionHost = { transaction: t };
  5. await this.userModel.create(
  6. { firstName: 'Abraham', lastName: 'Lincoln' },
  7. transactionHost,
  8. );
  9. await this.userModel.create(
  10. { firstName: 'John', lastName: 'Boothe' },
  11. transactionHost,
  12. );
  13. });
  14. } catch (err) {
  15. // Transaction has been rolled back
  16. // err is whatever rejected the promise chain returned to the transaction callback
  17. }
  18. }

Hint Note that the Sequelize instance is used only to start the transaction. However, to test this class would require mocking the entire Sequelize object (which exposes several methods). Thus, we recommend using a helper factory class (e.g., TransactionRunner) and defining an interface with a limited set of methods required to maintain transactions. This technique makes mocking these methods pretty straightforward.

Migrations

Migrations provide a way to incrementally update the database schema to keep it in sync with the application’s data model while preserving existing data in the database. To generate, run, and revert migrations, Sequelize provides a dedicated CLI.

Migration classes are separate from the Nest application source code. Their lifecycle is maintained by the Sequelize CLI. Therefore, you are not able to leverage dependency injection and other Nest specific features with migrations. To learn more about migrations, follow the guide in the Sequelize documentation.

Learn the right way!

  • Database - 图5 60+ chapters
  • Database - 图6 4+ hours of videos
  • Database - 图7 Official certificate
  • Database - 图8 Deep-dive sessions

Explore official courses

Multiple databases

Some projects require multiple database connections. This can also be achieved with this module. To work with multiple connections, first create the connections. In this case, connection naming becomes mandatory.

Suppose you have an Album entity stored in its own database.

  1. const defaultOptions = {
  2. dialect: 'postgres',
  3. port: 5432,
  4. username: 'user',
  5. password: 'password',
  6. database: 'db',
  7. synchronize: true,
  8. };
  9. @Module({
  10. imports: [
  11. SequelizeModule.forRoot({
  12. ...defaultOptions,
  13. host: 'user_db_host',
  14. models: [User],
  15. }),
  16. SequelizeModule.forRoot({
  17. ...defaultOptions,
  18. name: 'albumsConnection',
  19. host: 'album_db_host',
  20. models: [Album],
  21. }),
  22. ],
  23. })
  24. export class AppModule {}

Notice If you don’t set the name for a connection, its name is set to default. Please note that you shouldn’t have multiple connections without a name, or with the same name, otherwise they will get overridden.

At this point, you have User and Album models registered with their own connection. With this setup, you have to tell the SequelizeModule.forFeature() method and the @InjectModel() decorator which connection should be used. If you do not pass any connection name, the default connection is used.

  1. @Module({
  2. imports: [
  3. SequelizeModule.forFeature([User]),
  4. SequelizeModule.forFeature([Album], 'albumsConnection'),
  5. ],
  6. })
  7. export class AppModule {}

You can also inject the Sequelize instance for a given connection:

  1. @Injectable()
  2. export class AlbumsService {
  3. constructor(
  4. @InjectConnection('albumsConnection')
  5. private sequelize: Sequelize,
  6. ) {}
  7. }

Testing

When it comes to unit testing an application, we usually want to avoid making a database connection, keeping our test suites independent and their execution process as fast as possible. But our classes might depend on models that are pulled from the connection instance. How do we handle that? The solution is to create mock models. In order to achieve that, we set up custom providers. Each registered model is automatically represented by a <ModelName>Model token, where ModelName is the name of your model class.

The @nestjs/sequelize package exposes the getModelToken() function which returns a prepared token based on a given model.

  1. @Module({
  2. providers: [
  3. UsersService,
  4. {
  5. provide: getModelToken(User),
  6. useValue: mockModel,
  7. },
  8. ],
  9. })
  10. export class UsersModule {}

Now a substitute mockModel will be used as the UserModel. Whenever any class asks for UserModel using an @InjectModel() decorator, Nest will use the registered mockModel object.

Async configuration

You may want to pass your SequelizeModule options asynchronously instead of statically. In this case, use the forRootAsync() method, which provides several ways to deal with async configuration.

One approach is to use a factory function:

  1. SequelizeModule.forRootAsync({
  2. useFactory: () => ({
  3. dialect: 'mysql',
  4. host: 'localhost',
  5. port: 3306,
  6. username: 'root',
  7. password: 'root',
  8. database: 'test',
  9. models: [],
  10. }),
  11. });

Our factory behaves like any other asynchronous provider (e.g., it can be async and it’s able to inject dependencies through inject).

  1. SequelizeModule.forRootAsync({
  2. imports: [ConfigModule],
  3. useFactory: (configService: ConfigService) => ({
  4. dialect: 'mysql',
  5. host: configService.get('HOST'),
  6. port: +configService.get('PORT'),
  7. username: configService.get('USERNAME'),
  8. password: configService.get('PASSWORD'),
  9. database: configService.get('DATABASE'),
  10. models: [],
  11. }),
  12. inject: [ConfigService],
  13. });

Alternatively, you can use the useClass syntax:

  1. SequelizeModule.forRootAsync({
  2. useClass: SequelizeConfigService,
  3. });

The construction above will instantiate SequelizeConfigService inside SequelizeModule and use it to provide an options object by calling createSequelizeOptions(). Note that this means that the SequelizeConfigService has to implement the SequelizeOptionsFactory interface, as shown below:

  1. @Injectable()
  2. class SequelizeConfigService implements SequelizeOptionsFactory {
  3. createSequelizeOptions(): SequelizeModuleOptions {
  4. return {
  5. dialect: 'mysql',
  6. host: 'localhost',
  7. port: 3306,
  8. username: 'root',
  9. password: 'root',
  10. database: 'test',
  11. models: [],
  12. };
  13. }
  14. }

In order to prevent the creation of SequelizeConfigService inside SequelizeModule and use a provider imported from a different module, you can use the useExisting syntax.

  1. SequelizeModule.forRootAsync({
  2. imports: [ConfigModule],
  3. useExisting: ConfigService,
  4. });

This construction works the same as useClass with one critical difference - SequelizeModule will lookup imported modules to reuse an existing ConfigService instead of instantiating a new one.

Example

A working example is available here.