typeorm-base-repository
v1.0.0
Published
Generic base repository for TypeORM 0.3+ with NestJS — pagination, soft-delete, bulk operations, transactions and relation helpers
Maintainers
Readme
typeorm-base-repository
Generic base repository for TypeORM 0.3+ with NestJS. Pagination, soft-delete, bulk operations, transactions, and relation helpers — zero boilerplate.
Installation
npm install typeorm-base-repositoryPeer dependencies:
typeorm >= 0.3.0,@nestjs/common >= 8.0.0
Before vs After
Before — you write ~90 lines per module, repeated across every feature:
// user.repository.ts ← copy-pasted in every module
async paginate(page, limit) {
const [data, total] = await this.repo.findAndCount({ skip: (page-1)*limit, take: limit });
return { data, total, page, totalPages: Math.ceil(total/limit), hasNext: ..., hasPrev: ... };
}
async findById(id) { ... }
async exists(email) { ... }
async softDelete(id) { ... }
async withTransaction(fn) { /* queryRunner boilerplate */ }
// × 10 modules = ~1000 lines of identical codeAfter — 6 lines per module, everything inherited:
@Injectable()
export class UserRepository extends GenericRepository<User> {
constructor(@InjectRepository(User) repo: Repository<User>) {
super(repo);
}
}Quick Start
1. Pick a base entity
| Class | Storage | UUID | Best for |
|---|---|---|---|
| BinaryV7BaseEntity | BINARY(16) | v7 time-sortable | High-volume tables ← recommended |
| BinaryV4BaseEntity | BINARY(16) | v4 random | Compact storage, no ordering needed |
| VarcharV7BaseEntity | VARCHAR(36) | v7 time-sortable | Readable IDs + ordering |
| VarcharV4BaseEntity | VARCHAR(36) | v4 random | Simplest setup |
import { Entity, Column, DeleteDateColumn } from 'typeorm';
import { BinaryV7BaseEntity } from 'typeorm-base-repository';
@Entity()
export class User extends BinaryV7BaseEntity {
@Column({ unique: true })
email: string;
@Column()
name: string;
@DeleteDateColumn()
deletedAt?: Date;
}2. Create your repository
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { GenericRepository } from 'typeorm-base-repository';
import { User } from './user.entity';
@Injectable()
export class UserRepository extends GenericRepository<User> {
constructor(@InjectRepository(User) repo: Repository<User>) {
super(repo);
}
}3. Use in your service
@Injectable()
export class UserService {
constructor(private readonly userRepo: UserRepository) {}
// Paginate
findAll(page: number, limit: number) {
return this.userRepo.paginate({
page, limit,
relations: { organization: true },
orderBy: { createdAt: 'DESC' },
});
}
// Find or throw
findOne(id: string) {
return this.userRepo.findByIdOrFail(id, {
relations: { organization: true },
});
}
// Duplicate check + create
async create(dto: CreateUserDto) {
await this.userRepo.assertNotExists({ email: dto.email });
return this.userRepo.save(dto);
}
// Update
update(id: string, dto: UpdateUserDto) {
return this.userRepo.updateById(id, dto);
}
// Soft delete
remove(id: string) {
return this.userRepo.softDelete(id);
}
}API Reference
Find
// Find by primary key
repo.findById(id, { relations, select, withDeleted }) // → Entity | null
repo.findByIdOrFail(id, { relations, select }) // → Entity (throws NotFoundException)
// Find by criteria
repo.findOne(where, { relations, select, orderBy }) // → Entity | null
repo.findOneOrFail(where, { relations, select }) // → Entity (throws NotFoundException)
repo.findAll({ where, relations, select, orderBy }) // → Entity[]Pagination
repo.paginate({
page: 1,
limit: 20,
where: { status: 'ACTIVE' },
relations: { organization: { country: true } },
orderBy: { createdAt: 'DESC' },
withDeleted: false,
})
// → { data, total, page, limit, totalPages, hasNext, hasPrev }Paginate a nested relation:
repo.paginateRelation({
id: userId,
relation: 'invoices',
page: 1,
limit: 20,
orderBy: { createdAt: 'DESC' },
})
// → { data: User, relation: { data: Invoice[], total, page, ... } }Existence & Count
repo.exists(where) // → boolean
repo.assertNotExists(where) // throws ConflictException if found
repo.count(where) // → numberMutations
repo.save(data) // create + persist
repo.saveMany(data[]) // create + persist multiple
repo.updateById(id, data) // find + merge + persist (throws if not found)
repo.upsert(data, { conflictKey }) // insert or updateBulk
repo.bulkInsert(rows, { chunkSize: 500 }) // chunked INSERT
repo.bulkSoftDelete(ids, { chunkSize: 500 }) // chunked soft deleteSoft Delete
Requires
@DeleteDateColumn() deletedAt?: Dateon the entity.
repo.softDelete(id) // soft delete (throws if not found)
repo.restore(id) // restore
repo.findWithDeleted(id) // find including soft-deleted
repo.findOnlyDeleted(opts) // find only soft-deleted recordsTransaction
await repo.withTransaction(async (manager) => {
await manager.save(Order, order);
await manager.save(Invoice, invoice);
// auto-rollback on any thrown error
});UUID Utilities
import { generateUuidV4, generateUuidV7, isValidUuid } from 'typeorm-base-repository';
generateUuidV4() // "550e8400-e29b-41d4-a716-446655440000"
generateUuidV7() // "01900000-0000-7000-8000-000000000000" — time-sortable
isValidUuid(value) // type guard → booleanBoth generators are pure Node.js — no external dependencies.
MySQL Schema Example
CREATE TABLE `user` (
`id` BINARY(16) NOT NULL,
`email` VARCHAR(255) NOT NULL UNIQUE,
`name` VARCHAR(255) NOT NULL,
`deleted_at` DATETIME NULL,
PRIMARY KEY (`id`),
INDEX `IDX_user_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;License
MIT
