Sequelize Integration
Sequelize is a mature, battle-tested ORM for SQL databases, and the official @nestjs/sequelize package marries it with sequelize-typescript so you can define models as decorated classes and inject them through Nest’s dependency-injection container. With one SequelizeModule.forRoot call you describe the connection and your models become first-class providers. This page covers installing the packages, configuring the connection, defining models with @Table and @Column, injecting them into services, and wiring up associations and transactions.
Installing the packages
You need the Nest integration, the Sequelize core, the sequelize-typescript decorator layer, and a driver for your database. For PostgreSQL install pg and pg-hstore; swap them for mysql2, mariadb, sqlite3, or tedious depending on your engine.
npm install @nestjs/sequelize sequelize sequelize-typescript pg pg-hstore
npm install --save-dev @types/sequelize
sequelize-typescriptrelies on decorator metadata, so make sureexperimentalDecoratorsandemitDecoratorMetadataare both enabled intsconfig.json. The default Nest CLI project already sets these.
Configuring the connection with forRoot
The simplest setup passes an options object to SequelizeModule.forRoot in your root module. Nest creates the Sequelize instance during bootstrap and registers it as a provider so models and the connection itself can be injected anywhere.
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { User } from './users/user.model';
@Module({
imports: [
SequelizeModule.forRoot({
dialect: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'app',
models: [User],
autoLoadModels: true,
synchronize: false,
}),
],
})
export class AppModule {}
Setting autoLoadModels: true means any model registered through SequelizeModule.forFeature([...]) in a feature module is added automatically, so you rarely need to maintain the global models array by hand.
Never enable
synchronize: truein production. It runssequelize.sync()on boot and can alter or drop columns to match your models. Manage schema changes with thesequelize-climigration tooling instead.
Common connection options
These are the options you reach for most often when wiring up forRoot.
| Option | Type | Purpose |
|---|---|---|
dialect | string | Database driver: postgres, mysql, mariadb, sqlite, mssql. |
host / port | string / number | Server location for networked databases. |
models | array | Model classes to register on the connection. |
autoLoadModels | boolean | Auto-register models from forFeature calls. |
synchronize | boolean | Run sync() on boot. Development only. |
logging | boolean / function | Log generated SQL (default console.log). |
retryAttempts | number | How many times to retry the initial connect (default 10). |
Defining models with decorators
A model is a class that extends Model and is annotated with @Table. Each persisted field gets a @Column decorator, and sequelize-typescript infers the column type from the TypeScript type where it can, or you supply a DataType explicitly.
import { Column, DataType, Model, PrimaryKey, Table } from 'sequelize-typescript';
@Table({ tableName: 'users', timestamps: true })
export class User extends Model<User> {
@PrimaryKey
@Column({ type: DataType.UUID, defaultValue: DataType.UUIDV4 })
declare id: string;
@Column({ type: DataType.STRING, allowNull: false })
declare name: string;
@Column({ type: DataType.STRING, unique: true, allowNull: false })
declare email: string;
@Column({ type: DataType.BOOLEAN, defaultValue: true })
declare isActive: boolean;
}
The declare keyword tells TypeScript these properties are provided at runtime by Sequelize, preventing the compiler from emitting field initializers that would shadow the getters and setters Sequelize installs.
Injecting models into services
Register the model in its feature module with forFeature, then inject it with the @InjectModel decorator. The injected token is the model class itself, which exposes the full static query API.
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { User } from './user.model';
import { UsersService } from './users.service';
@Module({
imports: [SequelizeModule.forFeature([User])],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { User } from './user.model';
@Injectable()
export class UsersService {
constructor(@InjectModel(User) private readonly userModel: typeof User) {}
create(data: { name: string; email: string }): Promise<User> {
return this.userModel.create(data as User);
}
findAll(): Promise<User[]> {
return this.userModel.findAll();
}
async findOne(id: string): Promise<User> {
const user = await this.userModel.findByPk(id);
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}
return user;
}
}
Configuring associations
Relationships are declared with the association decorators — @HasMany, @BelongsTo, @HasOne, and @BelongsToMany — paired with @ForeignKey on the owning side. Here a user has many posts.
import {
BelongsTo, Column, DataType, ForeignKey, Model, Table,
} from 'sequelize-typescript';
import { User } from '../users/user.model';
@Table({ tableName: 'posts' })
export class Post extends Model<Post> {
@Column({ type: DataType.STRING, allowNull: false })
declare title: string;
@ForeignKey(() => User)
@Column({ type: DataType.UUID })
declare userId: string;
@BelongsTo(() => User)
declare author: User;
}
import { Column, DataType, HasMany, Model, Table } from 'sequelize-typescript';
import { Post } from '../posts/post.model';
@Table({ tableName: 'users' })
export class User extends Model<User> {
@Column({ type: DataType.STRING })
declare name: string;
@HasMany(() => Post)
declare posts: Post[];
}
To load related rows, pass the model to the include option. Eager-loading the author with each post produces a single joined query:
const posts = await this.postModel.findAll({ include: [User] });
console.log(posts[0].author.name);
Output:
Ada Lovelace
Running transactions
Wrap multiple writes in a managed transaction so they commit or roll back together. Inject the Sequelize instance and call transaction; the callback receives a transaction object you pass to each query via the transaction option.
import { Injectable } from '@nestjs/common';
import { InjectConnection, InjectModel } from '@nestjs/sequelize';
import { Sequelize } from 'sequelize-typescript';
import { User } from '../users/user.model';
import { Post } from './post.model';
@Injectable()
export class PostsService {
constructor(
@InjectConnection() private readonly sequelize: Sequelize,
@InjectModel(Post) private readonly postModel: typeof Post,
) {}
async publishWithAuthor(name: string, title: string): Promise<Post> {
return this.sequelize.transaction(async (t) => {
const author = await User.create({ name } as User, { transaction: t });
return this.postModel.create(
{ title, userId: author.id } as Post,
{ transaction: t },
);
});
}
}
Because the managed form returns the callback’s result and auto-commits on success, any thrown error inside the callback rolls the whole unit of work back — no manual commit or rollback calls required.
Best practices
- Keep
synchronizeoff in production and manage schema changes withsequelize-climigrations. - Load all connection settings from environment variables via
forRootAsyncandConfigService— never commit credentials. - Prefer
autoLoadModels: truewith per-featureforFeatureregistration over a single globalmodelsarray. - Use
declareon model fields so TypeScript does not emit initializers that clobber Sequelize’s accessors. - Reach for the managed
sequelize.transaction(cb)form so commit and rollback are handled for you. - Eager-load associations with
includeonly when you need them; over-fetching joins hurts query performance. - Enable
loggingin non-production environments to inspect the SQL Sequelize generates.