@sftech/nestjs-db
v1.0.2
Published
Database layer library providing TypeORM-based database access with CRUD patterns and OData support.
Readme
@sftech/nestjs-db
Database layer library providing TypeORM-based database access with CRUD patterns and OData support.
Installation
npm install @sftech/nestjs-dbPeer Dependencies
@sftech/nestjs-core >= 1.0.0@nestjs/typeormtypeorm
Features
- CRUD use cases (get-all, get-by-id, insert, update, delete, bulk-save)
- CRUD bundles for rapid development
- TypeORM repository pattern implementation
- OData query support via
odata-v4-typeorm - Domain <-> Infrastructure entity mapping
- Base controller with standard REST endpoints
Configuration
import { NestjsDbModule, DatabaseOptionsMapper } from '@sftech/nestjs-db';
import { NestjsCoreModule } from '@sftech/nestjs-core';
@Module({
imports: [
NestjsCoreModule.register(),
NestjsDbModule.register(DatabaseOptionsMapper.map()),
],
})
export class AppModule {}API Reference
Application Layer
| Class | Purpose |
|-------|---------|
| BaseCrudBundle<TDbModel> | Bundle of all CRUD use cases |
| BaseCrudIdentifierBundle<TDbModel> | CRUD bundle with identifier-based lookup |
| GetByIdUsecase | Get single entity by ID |
| GetAllUsecase | Get all entities |
| InsertUsecase | Insert new entity |
| UpdateUsecase | Update existing entity |
| DeleteUsecase | Delete entity |
| BulkSaveUsecase | Save multiple entities |
| OdataUsecase | Query with OData syntax |
Infrastructure Layer
| Class | Purpose |
|-------|---------|
| BaseDbEntity | Base TypeORM entity with id and createdAt |
| BaseDbRepository<TDbModel, TDbEntity> | Base repository implementation |
| BaseDbMapper<TDbModel, TDbEntity> | Base mapper for domain <-> entity conversion |
Presentation Layer
| Class | Purpose |
|-------|---------|
| BaseCrudController | Base controller with CRUD endpoints |
| BaseCrudIdentifierController | Controller with identifier-based routes |
| BaseDbCreateRequestDto | Base DTO for create requests |
| BaseDbUpdateRequestDto | Base DTO for update requests |
| BaseDtoDbModelConverter | Convert DTOs to domain models |
Base Classes & Interfaces
BaseCrudBundle
Bundle containing all CRUD use cases. Instantiate this in your module to get ready-to-use CRUD operations.
Generic type parameters:
TDbModel extends IBaseDbModel- Your domain model
Provides out of the box:
repo- The injected repositorygetByIdUsecase- Get entity by IDgetAllUsecase- Get all entitiesinsertUsecase- Insert new entityupdateUsecase- Update entitysaveUsecase- Insert or updatebulkSaveUsecase- Save multiple entitiesdeleteUsecase- Delete entityodataUsecase- OData queries
Constructor parameters:
_repo: IBaseDbRepository<TDbModel>- Repository instance
Usage example:
import { BaseCrudBundle, IBaseDbModel } from '@sftech/nestjs-db';
import { Injectable } from '@nestjs/common';
// 1. Define your domain model
export class User implements IBaseDbModel {
id?: number;
createdAt?: Date;
email: string;
name: string;
}
// 2. Create the bundle
@Injectable()
export class UserCrudBundle extends BaseCrudBundle<User> {
constructor(userRepository: UserRepository) {
super(userRepository);
}
}
// 3. Register in module
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
providers: [UserMapper, UserRepository, UserCrudBundle],
exports: [UserCrudBundle],
})
export class UserModule {}
// 4. Use in controller
@Controller('users')
export class UserController {
constructor(private readonly userBundle: UserCrudBundle) {}
@Get()
async getAll() {
return this.userBundle.getAllUsecase.execute();
}
@Get(':id')
async getById(@Param('id') id: number) {
return this.userBundle.getByIdUsecase.execute({ id, detailed: false });
}
@Post()
async create(@Body() dto: CreateUserDto) {
const user = new User();
user.email = dto.email;
user.name = dto.name;
return this.userBundle.insertUsecase.execute(user);
}
@Put(':id')
async update(@Param('id') id: number, @Body() dto: UpdateUserDto) {
const user = await this.userBundle.getByIdUsecase.execute({ id, detailed: false });
user.name = dto.name;
return this.userBundle.updateUsecase.execute(user);
}
@Delete(':id')
async delete(@Param('id') id: number) {
return this.userBundle.deleteUsecase.execute(id);
}
}BaseDbRepository<TDbModel, TDbEntity>
Abstract base repository providing all standard database operations. Extend this class for each entity.
Generic type parameters:
TDbModel extends IBaseDbModel- Your domain modelTDbEntity extends IBaseDbEntity- Your TypeORM entity
You must implement:
_mapper: IBaseDbMapper<TDbModel, TDbEntity>- Mapper for domain <-> entity conversion
Provides out of the box:
get(id, detailed?)- Get by ID with optional relationsgetAll()- Get all entitiesgetByOdata(query)- OData query supportfindOneBy(options)- Find single by optionsfindBy(options)- Find multiple by optionsinsert(obj, saveRelations?)- Insert new entityupdate(obj, saveRelations?)- Update existing entitysave(obj, saveRelations?)- Insert or updatebulkSave(objs)- Save multiple entitiesdelete(id)- Delete by ID
Usage example:
import { BaseDbRepository, IBaseDbMapper } from '@sftech/nestjs-db';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class UserRepository extends BaseDbRepository<User, UserEntity> {
protected readonly _mapper: IBaseDbMapper<User, UserEntity>;
constructor(
@InjectRepository(UserEntity)
repo: Repository<UserEntity>,
mapper: UserMapper,
) {
super(repo);
this._mapper = mapper;
}
// Add custom methods if needed
async findByEmail(email: string): Promise<User | null> {
return this.findOneBy({ where: { email } });
}
}BaseDbMapper<TDbModel, TDbEntity>
Abstract mapper for converting between domain models and TypeORM entities. Implement this for each entity.
Generic type parameters:
TDbModel extends IBaseDbModel- Your domain modelTDbEntity extends IBaseDbEntity- Your TypeORM entity
You must implement:
mapFromDb(from: TDbEntity, to?: TDbModel): TDbModel- Entity to domainmapToDb(from: TDbModel, to?: TDbEntity): TDbEntity- Domain to entity
Usage example:
import { BaseDbMapper } from '@sftech/nestjs-db';
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserMapper extends BaseDbMapper<User, UserEntity> {
mapFromDb(from: UserEntity, to?: User): User {
const user = to ?? new User();
user.id = from.id;
user.createdAt = from.createdAt;
user.email = from.email;
user.name = from.name;
return user;
}
mapToDb(from: User, to?: UserEntity): UserEntity {
const entity = to ?? new UserEntity();
entity.id = from.id;
entity.email = from.email;
entity.name = from.name;
return entity;
}
}BaseDbEntity
Base TypeORM entity with standard fields. Extend this for all your entities.
Provides:
id?: number- Primary key (auto-generated)createdAt?: Date- Creation timestamp (auto-generated)
Usage example:
import { BaseDbEntity } from '@sftech/nestjs-db';
import { Entity, Column } from 'typeorm';
@Entity('users')
export class UserEntity extends BaseDbEntity {
@Column()
email: string;
@Column()
name: string;
@Column({ default: true })
isActive: boolean;
}BaseCrudController<TDbModel, TResponseDto, TCreateDto, TUpdateDto>
Abstract controller providing standard CRUD REST endpoints. Extend this to quickly create a full CRUD API.
Generic type parameters:
TDbModel extends IBaseDbModel- Domain modelTResponseDto extends IBaseDbPresenter- Response DTOTCreateDto extends BaseDbCreateRequestDto- Create request DTOTUpdateDto extends BaseDbUpdateRequestDto- Update request DTO
You must implement:
_converter: IBaseDtoDbModelConverter<TCreateDto, TUpdateDto, TDbModel>- DTO to model converter
Provides out of the box (REST endpoints):
GET /- Get all entitiesGET /odata- OData queryGET /:id- Get by IDPOST /- Create entityPOST /bulk-save- Bulk save entitiesPUT /:id- Update entityDELETE /:id- Delete entity
Usage example:
import { BaseCrudController } from '@sftech/nestjs-db';
import { Controller } from '@nestjs/common';
@Controller('users')
export class UserController extends BaseCrudController<
User,
UserResponseDto,
CreateUserDto,
UpdateUserDto
> {
protected readonly _converter: UserDtoConverter;
constructor(
userBundle: UserCrudBundle,
converter: UserDtoConverter,
) {
super(userBundle);
this._converter = converter;
}
}
// Now you have all CRUD endpoints automatically:
// GET /users -> getAll()
// GET /users/odata -> getByOdata()
// GET /users/:id -> getById()
// POST /users -> create()
// POST /users/bulk-save -> bulkSave()
// PUT /users/:id -> update()
// DELETE /users/:id -> delete()BaseDtoDbModelConverter<TCreateDto, TUpdateDto, TDbModel>
Abstract converter for transforming DTOs to domain models. Implement this for DTO-to-model conversion.
You must implement:
convertCreateDtoToDbModel(dto: TCreateDto): Promise<TDbModel>convertUpdateDtoToDbModel(dto: TUpdateDto, existing: TDbModel): Promise<TDbModel>
Usage example:
import { BaseDtoDbModelConverter } from '@sftech/nestjs-db';
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserDtoConverter extends BaseDtoDbModelConverter<
CreateUserDto,
UpdateUserDto,
User
> {
async convertCreateDtoToDbModel(dto: CreateUserDto): Promise<User> {
const user = new User();
user.email = dto.email;
user.name = dto.name;
return user;
}
async convertUpdateDtoToDbModel(dto: UpdateUserDto, existing: User): Promise<User> {
if (dto.name !== undefined) {
existing.name = dto.name;
}
if (dto.email !== undefined) {
existing.email = dto.email;
}
return existing;
}
}IBaseDbModel
Interface for domain models. Implement this for all your domain models.
Required properties:
id?: number- Entity IDcreatedAt?: Date- Creation timestamp
Usage example:
import { IBaseDbModel } from '@sftech/nestjs-db';
export class Article implements IBaseDbModel {
id?: number;
createdAt?: Date;
title: string;
content: string;
authorId: number;
publishedAt?: Date;
}Complete Example
Here's a complete example of setting up a User entity with full CRUD:
// === DOMAIN LAYER ===
// user.model.ts
import { IBaseDbModel } from '@sftech/nestjs-db';
export class User implements IBaseDbModel {
id?: number;
createdAt?: Date;
email: string;
name: string;
isActive: boolean = true;
}
// === INFRASTRUCTURE LAYER ===
// user.entity.ts
import { BaseDbEntity } from '@sftech/nestjs-db';
import { Entity, Column } from 'typeorm';
@Entity('users')
export class UserEntity extends BaseDbEntity {
@Column({ unique: true })
email: string;
@Column()
name: string;
@Column({ default: true })
isActive: boolean;
}
// user.mapper.ts
import { BaseDbMapper } from '@sftech/nestjs-db';
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserMapper extends BaseDbMapper<User, UserEntity> {
mapFromDb(from: UserEntity, to?: User): User {
const user = to ?? new User();
user.id = from.id;
user.createdAt = from.createdAt;
user.email = from.email;
user.name = from.name;
user.isActive = from.isActive;
return user;
}
mapToDb(from: User, to?: UserEntity): UserEntity {
const entity = to ?? new UserEntity();
if (from.id) entity.id = from.id;
entity.email = from.email;
entity.name = from.name;
entity.isActive = from.isActive;
return entity;
}
}
// user.repository.ts
import { BaseDbRepository, IBaseDbMapper } from '@sftech/nestjs-db';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class UserRepository extends BaseDbRepository<User, UserEntity> {
protected readonly _mapper: IBaseDbMapper<User, UserEntity>;
constructor(
@InjectRepository(UserEntity) repo: Repository<UserEntity>,
mapper: UserMapper,
) {
super(repo);
this._mapper = mapper;
}
}
// === APPLICATION LAYER ===
// user.bundle.ts
import { BaseCrudBundle } from '@sftech/nestjs-db';
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserCrudBundle extends BaseCrudBundle<User> {
constructor(repository: UserRepository) {
super(repository);
}
}
// === PRESENTATION LAYER ===
// user.controller.ts
import { Controller, Get, Post, Put, Delete, Param, Body, Query } from '@nestjs/common';
@Controller('users')
export class UserController {
constructor(private readonly userBundle: UserCrudBundle) {}
@Get()
async getAll() {
return this.userBundle.getAllUsecase.execute();
}
@Get('odata')
async getByOdata(@Query() query: OdataRequestQueryDto) {
return this.userBundle.odataUsecase.execute(query);
}
@Get(':id')
async getById(@Param('id') id: number) {
return this.userBundle.getByIdUsecase.execute({ id, detailed: false });
}
@Post()
async create(@Body() dto: CreateUserDto) {
const user = new User();
user.email = dto.email;
user.name = dto.name;
return this.userBundle.insertUsecase.execute(user);
}
@Put(':id')
async update(@Param('id') id: number, @Body() dto: UpdateUserDto) {
const user = await this.userBundle.getByIdUsecase.execute({ id, detailed: false });
user.name = dto.name ?? user.name;
return this.userBundle.updateUsecase.execute(user);
}
@Delete(':id')
async delete(@Param('id') id: number) {
return this.userBundle.deleteUsecase.execute(id);
}
}
// === MODULE ===
// user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
providers: [UserMapper, UserRepository, UserCrudBundle],
controllers: [UserController],
exports: [UserCrudBundle],
})
export class UserModule {}OData Support
The library supports OData queries via the odataUsecase:
// GET /users/odata?$top=10&$skip=0&$filter=name eq 'John'&$orderby=createdAt desc
@Get('odata')
async getByOdata(@Query() query: OdataRequestQueryDto) {
return this.userBundle.odataUsecase.execute(query);
}Supported OData parameters:
$top- Limit results$skip- Skip results (pagination)$filter- Filter expression$orderby- Sort order$expand- Include relations$count- Include total count
Development
# Build
npx nx build nestjs-db
# Format
npx biome format --write libs/nestjs-db/Changelog
See CHANGELOG.md
License
MIT
