This post covers TypeORM examples with the NestJS framework, from setting up the connection with the Postgres database to working with transactions. The following snippets can be adjusted and reused with other frameworks like Express. The same applies to SQL databases.
Prerequisites
- NestJS app bootstrapped
- Postgres database running
- Node.js version 26
- NestJS 11, TypeORM 1.0,
@nestjs/typeorm11, andpgpackages installed
Database connection
It requires the initialization of the DataSource configuration.
// app.module.ts
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) =>
configService.get('database'),
dataSourceFactory: async (options) => new DataSource(options).initialize()
});
DataSource configuration contains elements for the connection string, migration details, etc.
// config/database.ts
import { registerAs } from '@nestjs/config';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export default registerAs(
'database',
(): TypeOrmModuleOptions => ({
type: 'postgres',
host: process.env.DATABASE_HOSTNAME,
port: Number(process.env.DATABASE_PORT),
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
logging: false,
synchronize: false,
migrationsRun: false,
autoLoadEntities: true
})
);
autoLoadEntities registers entities from TypeOrmModule.forFeature() calls. Run migrations with the CLI before starting the app, or set migrationsRun: true and list migration classes in the DataSource options.
Migrations and seeders
Migrations are handled with the following scripts for creation, running, and reverting.
// package.json
{
"scripts": {
"migration:create": "npm run typeorm -- migration:create",
"migrate": "npm run typeorm -- migration:run -d src/config/ormconfig-migration.ts",
"migrate:down": "npm run typeorm -- migration:revert -d src/config/ormconfig-migration.ts",
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js"
}
}
A new migration template is created at the provided path with the following command. The filename is in the format <TIMESTAMP>-<MIGRATION_NAME>.ts.
npm run migration:create database/migrations/<MIGRATION_NAME>
Here is the example for the migration which creates a new table. A table is dropped when the migration is reverted.
// database/migrations/1669833880587-create-users.ts
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
export class CreateUsers1669833880587 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
await queryRunner.createTable(
new Table({
name: 'users',
columns: [
{
name: 'id',
type: 'uuid',
default: 'uuid_generate_v4()',
generationStrategy: 'uuid',
isGenerated: true,
isPrimary: true
},
{
name: 'first_name',
type: 'varchar'
}
]
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('users');
}
}
Scripts for running and reverting the migrations require a separate DataSource configuration. Import migration classes explicitly - glob paths are unreliable with the TypeORM CLI.
// config/ormconfig-migration.ts
import 'dotenv/config';
import { DataSource } from 'typeorm';
import { CreateUsers1669833880587 } from '../../database/migrations/1669833880587-create-users';
import { UsersEntity } from '../users/users.entity';
export default new DataSource({
type: 'postgres',
host: process.env.DATABASE_HOSTNAME,
port: Number(process.env.DATABASE_PORT),
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
entities: [UsersEntity],
migrations: [CreateUsers1669833880587],
migrationsTableName: 'migrations',
logging: true,
synchronize: false
});
Seeder is a type of migration. Seeders are handled with the following scripts for creation, running, and reverting.
// package.json
{
"scripts": {
"seed:generate": "npm run typeorm -- migration:create",
"seed": "npm run typeorm -- migration:run -d src/config/ormconfig-seeder.ts",
"seed:down": "npm run typeorm -- migration:revert -d src/config/ormconfig-seeder.ts"
}
}
A new seeder template is created at the provided path with the following command. The filename is in the format <TIMESTAMP>-<SEEDER_NAME>.ts.
npm run seed:generate database/seeders/<SEEDER_NAME>
Here is the example for the seeder which inserts some data. Table data is removed when the seeder is reverted.
// database/seeders/1669834539569-add-users.ts
import { MigrationInterface, QueryRunner } from 'typeorm';
import { UsersEntity } from '../../src/users/users.entity';
export class AddUsers1669834539569 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.manager.insert(UsersEntity, [
{
firstName: 'tester'
}
]);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.manager.clear(UsersEntity);
}
}
Scripts for running and reverting the seeders require a separate DataSource configuration. The seeders table name is seeders in this case.
// config/ormconfig-seeder.ts
import 'dotenv/config';
import { DataSource } from 'typeorm';
import { AddUsers1669834539569 } from '../../database/seeders/1669834539569-add-users';
import { UsersEntity } from '../users/users.entity';
export default new DataSource({
type: 'postgres',
host: process.env.DATABASE_HOSTNAME,
port: Number(process.env.DATABASE_PORT),
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
entities: [UsersEntity],
migrations: [AddUsers1669834539569],
migrationsTableName: 'seeders',
logging: true,
synchronize: false
});
Entities
Entities are specified with their columns and Entity decorator.
// users.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity({ name: 'users' })
export class UsersEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'first_name' })
firstName: string;
}
Entities should be registered with the forFeature method.
// users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([UsersEntity])],
providers: [UsersService, UsersRepository]
})
export class UsersModule {}
Custom repositories
Custom repositories extend the base repository class and enrich it with several additional methods.
// users.repository.ts
@Injectable()
export class UsersRepository extends Repository<UsersEntity> {
constructor(private dataSource: DataSource) {
super(UsersEntity, dataSource.createEntityManager());
}
async getById(id: string) {
return this.findOne({ where: { id } });
}
}
Custom repositories should be registered as a provider.
// users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([UsersEntity])],
providers: [UsersService, UsersRepository]
})
export class UsersModule {}
Testing custom repositories
Testing custom repositories (NestJS/TypeORM) post covers unit and integration testing. Runnable unit test lives in the testing-custom-repositories-demo folder.
Transactions
typeorm-transactional uses CLS or AsyncLocalStorage to propagate transactions across repositories and service methods. It supports the TypeORM DataSource API and works with custom repositories.
npm install typeorm-transactional
@Injectable()
export class PostService {
constructor(
private readonly authorRepository: AuthorRepository,
private readonly postRepository: PostRepository
) {}
@Transactional()
async createPost(authorUsername: string, message: string): Promise<PostEntity> {
const author = this.authorRepository.create({ username: authorUsername });
await this.authorRepository.save(author);
return this.postRepository.save({
message,
authorId: author.id
});
}
}
Initialize the transactional context before the NestJS app starts.
// main.ts
import {
initializeTransactionalContext,
StorageDriver
} from 'typeorm-transactional';
async function bootstrap(): Promise<void> {
initializeTransactionalContext({ storageDriver: StorageDriver.AUTO });
// ...
}
Register the DataSource with the transactional context.
import { addTransactionalDataSource } from 'typeorm-transactional';
dataSourceFactory: async (options) => {
if (!options) {
throw new Error('Invalid TypeORM options');
}
return addTransactionalDataSource(new DataSource(options)).initialize();
}
TypeORM also exposes dataSource.transaction() if you prefer explicit transaction blocks without a decorator.
Demo
Sample NestJS app for this post lives in the typeorm-nestjs-demo folder. Get access via code demos.
Top comments (0)