@flusys/nestjs-shared
v5.2.0
Published
Common shared utilities for Flusys NestJS applications
Maintainers
Readme
@flusys/nestjs-shared
Shared infrastructure for all FLUSYS NestJS packages — base CRUD classes, JWT guard, permission guard, response DTOs, hybrid cache, structured logging, and soft-delete entities.
Installation
npm install @flusys/nestjs-shared @flusys/nestjs-core
npm install typeorm @nestjs/passport @nestjs/jwt passport-jwt winston1. Module Registration
Register once in your root AppModule:
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { CacheModule, UtilsModule } from '@flusys/nestjs-shared/modules';
import { LoggerMiddleware } from '@flusys/nestjs-shared/middlewares';
import { GlobalExceptionFilter } from '@flusys/nestjs-shared/filters';
import { ResponseMetaInterceptor } from '@flusys/nestjs-shared/interceptors';
@Module({
imports: [
CacheModule.forRoot(true), // true = global; provides 'CACHE_INSTANCE' token
UtilsModule, // provides UtilsService
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}
// In bootstrap()
app.useGlobalFilters(new GlobalExceptionFilter());
app.useGlobalInterceptors(new ResponseMetaInterceptor());2. Entity Base Class
Every entity must extend Identity:
import { Entity, Column } from 'typeorm';
import { Identity } from '@flusys/nestjs-shared/entities';
@Entity('products')
export class Product extends Identity {
@Column({ length: 255 })
name: string;
// inherited: id (uuid), createdAt, updatedAt, deletedAt,
// createdById, updatedById, deletedById
}3. Generic CRUD — createApiController + RequestScopedApiService
Service (REQUEST-scoped, DataSource Provider pattern)
import { Injectable, Scope, Inject } from '@nestjs/common';
import { EntityTarget, Repository } from 'typeorm';
import { RequestScopedApiService } from '@flusys/nestjs-shared/classes';
import { HybridCache } from '@flusys/nestjs-shared/classes';
import { UtilsService } from '@flusys/nestjs-shared/modules';
@Injectable({ scope: Scope.REQUEST }) // required
export class ProductService extends RequestScopedApiService<
CreateProductDto,
UpdateProductDto,
IProduct,
Product
> {
constructor(
@Inject('CACHE_INSTANCE') protected override cacheManager: HybridCache,
@Inject(UtilsService) protected override utilsService: UtilsService,
@Inject(ProductConfigService) private readonly config: ProductConfigService,
@Inject(ProductDataSourceProvider) private readonly dsp: ProductDataSourceProvider,
) {
super('product', null as any, cacheManager, utilsService, ProductService.name, true);
}
// Required: tell the base class which entity and which datasource to use
protected resolveEntity(): EntityTarget<Product> {
return this.config.isCompanyFeatureEnabled() ? ProductWithCompany : Product;
}
protected getDataSourceProvider() {
return this.dsp;
}
}Controller (factory function)
import { Controller, Inject } from '@nestjs/common';
import { createApiController } from '@flusys/nestjs-shared/classes';
@Controller('administration/products')
export class ProductController extends createApiController<
CreateProductDto,
UpdateProductDto,
ProductResponseDto,
IProduct,
ProductService
>(CreateProductDto, UpdateProductDto, ProductResponseDto, {
entityName: 'product',
security: {
insert: { level: 'permission', permissions: ['product.create'] },
insertMany: { level: 'permission', permissions: ['product.create'] },
getAll: { level: 'permission', permissions: ['product.read'] },
getById: { level: 'permission', permissions: ['product.read'] },
update: { level: 'permission', permissions: ['product.update'] },
updateMany: { level: 'permission', permissions: ['product.update'] },
delete: { level: 'permission', permissions: ['product.delete'] },
bulkUpsert: { level: 'permission', permissions: ['product.create'] },
getByIds: { level: 'permission', permissions: ['product.read'] },
getByFilter: { level: 'permission', permissions: ['product.read'] },
},
}) {
constructor(@Inject(ProductService) public override service: ProductService) {
super(service);
}
}Generated POST-only endpoints: /insert, /insert-many, /get-all, /get/:id,
/get-by-ids, /get-by-filter, /update, /update-many, /bulk-upsert, /delete.
Security levels: 'public' (no auth), 'jwt' (token only), 'permission' (token + action check).
4. Guards and Decorators
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
import { PermissionGuard } from '@flusys/nestjs-shared/guards';
import {
CurrentUser,
Public,
RequirePermission,
RequireAnyPermission,
} from '@flusys/nestjs-shared/decorators';
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
@UseGuards(JwtAuthGuard, PermissionGuard)
@RequirePermission('product.create')
@Post('insert')
async insert(@Body() dto: CreateProductDto, @CurrentUser() user: ILoggedUserInfo) { }
// Mark endpoint public (no JWT required)
@Public()
@Post('public-list')
async publicList() { }ILoggedUserInfo fields: id, email, name?, phone?, profilePictureId?, companyId?, branchId?.
5. Response DTOs
All controllers return one of these shapes:
import {
SingleResponseDto, // insert, update, getById
ListResponseDto, // getAll, getByIds
BulkResponseDto, // insertMany, updateMany, bulkUpsert
MessageResponseDto, // delete
} from '@flusys/nestjs-shared/dtos';
// All shapes include: success, message, messageKey (for i18n), _meta (requestId, timestamp, duration)
// ListResponseDto / BulkResponseDto also include: meta (total, page, pageSize, count, totalPages)6. Exceptions
import {
NotFoundException,
ConflictException,
UnauthorizedException,
ForbiddenException,
ValidationException,
InternalServerException,
} from '@flusys/nestjs-shared/exceptions';
throw new NotFoundException({
message: 'User not found',
messageKey: USER_MESSAGES.NOT_FOUND,
});
throw new ConflictException({
message: `User already assigned to this ${typeName}. Please refresh and try again.`,
messageKey: USER_PERMISSION_MESSAGES.ALREADY_ASSIGNED,
messageVariables: { type: typeName },
});
throw new ValidationException([{ field: 'email', message: 'Invalid format' }]);All exceptions produce: { success: false, message, messageKey,messageVariables:{} code, _meta }.
7. Hybrid Cache
import { HybridCache } from '@flusys/nestjs-shared/classes';
@Injectable()
export class MyService {
constructor(@Inject('CACHE_INSTANCE') private cache: HybridCache) {}
async getData(key: string) {
const cached = await this.cache.get(key);
if (cached) return cached;
const data = await this.fetchFromDb();
await this.cache.set(key, data, 3600); // TTL in seconds
return data;
}
async invalidate(key: string) {
await this.cache.delete(key);
}
async invalidatePrefix(prefix: string) {
await this.cache.deleteByPrefix(prefix);
}
}CacheModule.forRoot(true) connects to Redis automatically when REDIS_URL is set; otherwise uses in-memory only.
License
MIT © FLUSYS
