npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@artenc/nest-orm

v2.1.0

Published

Helper utility to work with TypeORM

Readme

NestJS ORM

Helper library to work with TypeORM in NestJS: a base entity with a transaction-aware save(), a generic CRUD service mixin, a binary UUID column type, and automatic transaction propagation via AsyncLocalStorage.

Key features

  • Transaction-aware entities — an entity's save() always writes through the right connection, whether that's its repository or an active transaction, with no manual bookkeeping.
  • Generic CRUD service — get create/find/findOne/update/remove/... for an entity without hand-writing the same repository boilerplate every time.
  • Automatic transaction propagation — wrap a block of work in a transaction and every entity/service call inside it joins that transaction automatically, including nested calls.
  • Compact UUID storage — store UUIDs as 16-byte binary columns instead of 36-character strings, with optional auto-generation.

Installation

npm install @artenc/nest-orm

@nestjs/common, @nestjs/typeorm, and typeorm are peer dependencies — install them in your app if you haven't already (any NestJS + TypeORM project already has them).

Usage

OrmModule

The application imports OrmModule.forRoot() or OrmModule.forRootAsync() once. They take the same options as TypeOrmModule.forRoot()/forRootAsync() and set up the TypeORM connection, so the application doesn't import TypeOrmModule.forRoot*() itself:

import { Module } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { OrmModule } from '@artenc/nest-orm'

@Module({
    imports: [
        OrmModule.forRootAsync({
            imports: [ConfigModule],
            inject: [ConfigService],
            useFactory: (configService: ConfigService) => ({
                type: 'mysql',
                host: configService.getOrThrow<string>('db.host'),
                // ...
                autoLoadEntities: true,
            }),
            utc: true,
        }),
    ],
})
export class AppModule {}

Feature modules and libraries that need TransactionService import OrmModule itself, without options. OrmModule.forRoot*() re-exports that same module, so the application and all libraries share one TransactionService.

With utc: true, the connection uses a UtcDataSource. utc can't be combined with a custom dataSourceFactory.

UtcDataSource

A DataSource that stores and reads dates as UTC (timezone: 'Z'), and fails to initialize unless the database server runs in UTC too. The server fills DEFAULT CURRENT_TIMESTAMP columns such as @CreateDateColumn() in its own time zone, so the server and the driver must agree; otherwise those dates are off by the server's offset. Configure the server with default-time-zone = '+00:00'.

Supports MySQL and MariaDB; other databases are refused. OrmModule.forRootAsync({ utc: true }) uses it for the application, and the DataSource file can use it for TypeORM's CLI.

AbstractOrmEntity

An entity loaded or created through AbstractOrmService doesn't know, from the caller's side, whether it came from its repository or from an active TransactionService transaction — it's just an entity. AbstractOrmEntity is what makes that transparent: every entity gets a save() method that checks, at the moment it's called, whether a transaction is currently active and writes through it if so — joining whatever transaction is running then, regardless of whether one was active when the entity was originally loaded or created. If nothing is active at that moment, it falls back to a plain write through the entity's repository instead. Either way, there's no EntityManager to pass around by hand.

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
import { AbstractOrmEntity } from '@artenc/nest-orm'

@Entity()
export class User extends AbstractOrmEntity {
    @PrimaryGeneratedColumn()
    id!: number

    @Column()
    name!: string
}
const user = await userOrmService.findOne({ where: { id } })

user.name = 'Ada Lovelace'
await user.save() // joins whatever transaction is active right now — not necessarily the one that loaded `user`

You can also override fillable() to allow mass-assigning a specific set of fields from untrusted input (e.g. a request DTO) via apply(), instead of writing them out by hand one at a time. Its return type is (keyof this)[], not a plain string[], so a typo'd field name is a compile error rather than a silently-dropped field:

export class User extends AbstractOrmEntity {
    // ...columns

    protected fillable(): (keyof this)[] {
        return ['email', 'name']
    }
}
user.apply({ name: 'Ada Lovelace', role: 'admin' }) // 'role' is silently ignored, it's not fillable
await user.save()

BinaryUuidColumn

Stores a UUID as a 16-byte binary column instead of a 36-character string — smaller and faster to index — and can optionally generate the value for you on insert.

Supported databases: MySQL and SQL Server only. BinaryUuidColumn uses TypeORM's binary column type under the hood, which those two dialects support natively — Postgres and SQLite do not support a binary column type at all, so a DataSource with a BinaryUuidColumn entity fails to initialize on either of them.

import { Entity, Column } from 'typeorm'
import { AbstractOrmEntity, BinaryUuidColumn } from '@artenc/nest-orm'

@Entity()
export class User extends AbstractOrmEntity {
    @BinaryUuidColumn({ generated: true, primary: true })
    id!: string

    @Column()
    name!: string
}

BinaryUuidColumn accepts the usual TypeORM ColumnOptions (nullable, primary, unique, ...) plus:

| Option | Default | Description | | ----------- | ------- | -------------------------------------------------------------------------- | | generated | false | Auto-generate the value on insert if it's null/undefined. | | version | 'v7' | 'v7' (time-ordered, recommended for index locality) or 'v4' (random). |

generated: true is fully supported through nest-orm's own persistence path — AbstractOrmService.create() and AbstractOrmEntity.save() — via a @BeforeInsert hook defined on AbstractOrmEntity, so the entity class needs to extend it for auto-generation to actually run (a plain AbstractOrmEntity subclass like the User above already satisfies this). It isn't triggered by TypeORM's own repository.insert()/entityManager.insert() or a raw createQueryBuilder().insert() — those bypass entity listeners entirely, so provide the id yourself if you reach for those directly.

AbstractOrmService

AbstractOrmService(Entity) returns a class with create, exists, find, findOne, count, update, remove, and createQueryBuilder already implemented against Entity's repository — extend it instead of hand-writing the same boilerplate for every entity:

import { Injectable } from '@nestjs/common'
import { AbstractOrmService } from '@artenc/nest-orm'
import { User } from './user.entity'

@Injectable()
export class UserOrmService extends AbstractOrmService(User) {}
const user = await userOrmService.create({ email: '[email protected]', name: 'Ada' })

await userOrmService.findOne({ where: { id: user.id } })
await userOrmService.find({ where: { name: 'Ada' } })
await userOrmService.update({ id: user.id }, { name: 'Ada Lovelace' })
await userOrmService.remove({ id: user.id })

Register the entity's repository the usual way and provide the service:

import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { User } from './entities/user.entity'
import { UserOrmService } from './services/orm/user-orm.service'

@Module({
    imports: [TypeOrmModule.forFeature([User])],
    providers: [UserOrmService],
    exports: [UserOrmService],
})
export class UserModule {}

Every method takes an optional trailing enforceEntityManager argument to run against a specific EntityManager instead of the entity's default repository — useful outside of TransactionService (e.g. a manually managed QueryRunner).

TransactionService

Wraps a block of work in a real database transaction and makes the transaction's EntityManager ambiently available (via AsyncLocalStorage) to every AbstractOrmService/AbstractOrmEntity call made inside it — no need to thread an EntityManager through every method call by hand:

import { Injectable } from '@nestjs/common'
import { TransactionService } from '@artenc/nest-orm'
import { WalletService } from './wallet.service'

@Injectable()
export class TransferService {
    constructor(
        private readonly transactionService: TransactionService,
        private readonly walletService: WalletService,
    ) {}

    async transfer(fromUserId: string, toUserId: string, amount: number): Promise<void> {
        await this.transactionService.transaction(async () => {
            // Both calls below automatically join the same transaction,
            // and roll back together if either one throws.
            await this.walletService.debit(fromUserId, amount)
            await this.walletService.credit(toUserId, amount)
        })
    }
}

Calling transaction() again while one is already active (e.g. a service method that also wraps its own logic in a transaction, called from inside another transaction) opens a real nested transaction backed by a SQL SAVEPOINT — if the inner call fails, only its own work rolls back; the outer transaction is unaffected and can continue.

fn also receives the active EntityManager as an argument, for cases that need it directly — raw TypeORM repositories, createQueryBuilder(), or any third-party code expecting an EntityManager — instead of relying on AbstractOrmService/AbstractOrmEntity's automatic pickup:

await this.transactionService.transaction(async (manager) => {
    await manager.createQueryBuilder(User, 'user').where('user.id = :id', { id }).getOne()
})

Scripts

Typical TypeORM CLI scripts for a project consuming this library:

{
  "scripts": {
    "typeorm": "typeorm-ts-node-commonjs",
    "typeorm:query": "typeorm-ts-node-commonjs query -d dist/data-source.js",
    "migration:create": "typeorm-ts-node-commonjs migration:create",
    "migration:generate": "typeorm-ts-node-commonjs migration:generate -d dist/data-source.js",
    "migration:run": "typeorm-ts-node-commonjs migration:run -d dist/data-source.js",
    "migration:revert": "typeorm-ts-node-commonjs migration:revert -d dist/data-source.js"
  }
}

DataSource file example

The -d dist/data-source.js flag in the scripts above points TypeORM's CLI at this file: a plain, standalone DataSource instance the CLI can import and connect with directly. It's separate from the DataSource your NestJS app builds via OrmModule.forRoot(...) because the CLI runs as its own process outside Nest's DI container — migration:generate/migration:run/migration:revert/query have no other way to reach the same database connection your app uses at runtime.

orm-imports.ts — glob paths TypeORM expands at runtime to every compiled entity/migration file, so data-source.ts doesn't need to list them by hand:

import * as path from 'path'

export const entities = [path.join(__dirname, 'entities/*')]
export const migrations = [path.join(__dirname, 'migrations/*')]

data-source.ts (with utc: true in the application, use UtcDataSource here too, so migrations run with the same time zone handling):

import 'dotenv/config'
import { UtcDataSource } from '@artenc/nest-orm'
import { entities, migrations } from './orm-imports'

export default new UtcDataSource({
    type: 'mysql',
    host: process.env.DB_HOST,
    port: Number(process.env.DB_PORT),
    username: process.env.DB_USER,
    password: process.env.DB_PASS,
    database: process.env.DB_NAME,
    entities,
    migrations,
})

License

MIT