@flusys/nestjs-shared
v1.1.0
Published
Common shared utilities for Flusys NestJS applications
Maintainers
Readme
Shared Package Guide
Package:
@flusys/nestjs-sharedVersion: 1.1.0 Type: Shared NestJS utilities, classes, decorators, guards, and modules
This comprehensive guide covers the shared package - the shared NestJS infrastructure layer.
Table of Contents
- Overview
- Package Architecture
- ApiService - Generic CRUD Service
- RequestScopedApiService
- ApiController - Generic CRUD Controller
- Decorators
- Guards
- Middleware
- Interceptors
- Caching System
- Multi-Tenant DataSource
- DTOs
- Base Entities
- Utilities
- Error Handling
- Constants
- API Reference
Overview
@flusys/nestjs-shared provides shared utilities for building scalable NestJS applications:
- Generic CRUD - Standardized API controller and service patterns
- Permission System - Role and permission-based access control with complex logic
- Caching - In-memory + Redis hybrid caching (HybridCache)
- Request Correlation - AsyncLocalStorage-based request tracking
- Middleware - Logging, correlation, and performance monitoring
- Interceptors - Response metadata, idempotency, auto field setting
- Multi-Tenancy - Dynamic database connection management
- Error Handling - Centralized error handling with sensitive data redaction
Package Hierarchy
@flusys/nestjs-core <- Pure TypeScript (foundation)
|
@flusys/nestjs-shared <- Shared NestJS utilities (THIS PACKAGE)
|
@flusys/nestjs-auth <- Uses common classes
|
@flusys/nestjs-iam <- Uses common patterns
|
@flusys/nestjs-storage <- Uses common patternsPackage Architecture
nestjs-shared/
├── src/
│ ├── classes/ # Base classes
│ │ ├── api-service.class.ts # Generic CRUD service
│ │ ├── api-controller.class.ts # Generic CRUD controller factory (createApiController)
│ │ ├── request-scoped-api.service.ts # REQUEST-scoped service base
│ │ ├── hybrid-cache.class.ts # Two-tier caching
│ │ ├── winston.logger.class.ts # Winston logger config
│ │ └── winston-logger-adapter.class.ts
│ │
│ ├── constants/ # Injection tokens & constants
│ │ ├── permissions.ts # Permission constants (PERMISSIONS)
│ │ └── index.ts
│ │
│ ├── decorators/ # Custom decorators
│ │ ├── api-response.decorator.ts # @ApiResponseDto
│ │ ├── current-user.decorator.ts # @CurrentUser
│ │ ├── public.decorator.ts # @Public
│ │ ├── require-permission.decorator.ts # @RequirePermission, @RequireAnyPermission, @RequirePermissionCondition
│ │ ├── sanitize-html.decorator.ts # @SanitizeHtml, @SanitizeAndTrim
│ │ └── index.ts
│ │
│ ├── dtos/ # Shared DTOs
│ │ ├── delete.dto.ts
│ │ ├── filter-and-pagination.dto.ts
│ │ ├── identity-response.dto.ts
│ │ ├── pagination.dto.ts
│ │ ├── response-payload.dto.ts
│ │ └── index.ts
│ │
│ ├── entities/ # Base entities
│ │ ├── identity.ts # Base entity with UUID
│ │ ├── user-root.ts # Base user entity
│ │ └── index.ts
│ │
│ ├── exceptions/ # Custom exceptions
│ │ └── permission.exception.ts # Permission-related exceptions
│ │
│ ├── guards/ # Authentication & authorization
│ │ ├── jwt-auth.guard.ts # JWT token validation
│ │ └── permission.guard.ts # Permission checks
│ │
│ ├── interceptors/ # Request/response interceptors
│ │ ├── delete-empty-id-from-body.interceptor.ts
│ │ ├── idempotency.interceptor.ts
│ │ ├── query-performance.interceptor.ts
│ │ ├── response-meta.interceptor.ts
│ │ ├── set-user-field-on-body.interceptor.ts # SetCreatedByOnBody, SetUpdateByOnBody, SetDeletedByOnBody
│ │ └── slug.interceptor.ts
│ │
│ ├── interfaces/ # TypeScript interfaces
│ │ ├── api.interface.ts # IService interface
│ │ ├── datasource.interface.ts # IDataSourceProvider
│ │ ├── identity.interface.ts
│ │ ├── logged-user-info.interface.ts # ILoggedUserInfo
│ │ ├── logger.interface.ts # ILogger
│ │ ├── module-config.interface.ts # IModuleConfigService
│ │ └── permission.interface.ts # PermissionCondition, PermissionOperator
│ │
│ ├── middlewares/ # Middleware
│ │ └── logger.middleware.ts # Request logging & correlation
│ │
│ ├── modules/ # NestJS modules
│ │ ├── cache/cache.module.ts
│ │ ├── datasource/
│ │ │ ├── datasource.module.ts
│ │ │ └── multi-tenant-datasource.service.ts
│ │ └── utils/
│ │ ├── utils.module.ts
│ │ └── utils.service.ts
│ │
│ └── utils/ # Utility functions
│ ├── error-handler.util.ts
│ ├── query-helpers.util.ts
│ ├── string.util.ts
│ ├── request.util.ts
│ └── html-sanitizer.util.tsApiService - Generic CRUD Service
The ApiService base class provides standardized CRUD operations with caching, pagination, and transaction support.
Basic Usage
import { ApiService, HybridCache } from '@flusys/nestjs-shared/classes';
import { UtilsService } from '@flusys/nestjs-shared/modules';
import { Injectable, Inject } from '@nestjs/common';
import { Repository } from 'typeorm';
@Injectable()
export class UserService extends ApiService<
CreateUserDto, // Create DTO
UpdateUserDto, // Update DTO
IUser, // Interface
User, // Entity
Repository<User> // Repository type
> {
constructor(
@InjectRepository(User)
protected override repository: Repository<User>,
@Inject('CACHE_INSTANCE')
protected override cacheManager: HybridCache,
@Inject(UtilsService)
protected override utilsService: UtilsService,
) {
super(
'user', // Entity name (for query building)
repository,
cacheManager,
utilsService,
'UserService', // Service name (for logging)
true, // Enable caching
);
}
}ApiService Methods
| Method | Description |
|--------|-------------|
| insert(dto, user) | Create single entity |
| insertMany(dtos, user) | Create multiple entities |
| getById(id, user, select?) | Get entity by ID |
| findById(id, user, select?) | Find entity (returns null if not found) |
| findByIds(ids, user, select?) | Find multiple by IDs |
| getAll(dto, user) | Get paginated list |
| update(dto, user) | Update single entity |
| updateMany(dtos, user) | Update multiple entities |
| delete(dto, user) | Soft/permanent delete or restore |
Customization Hooks
@Injectable()
export class UserService extends ApiService<...> {
// Convert DTO to entity
override async convertSingleDtoToEntity(dto, user): Promise<User> { }
// Customize SELECT query
override async getSelectQuery(query, user, select?): Promise<{ query, isRaw }> { }
// Add WHERE filters
override async getFilterQuery(query, filter, user): Promise<{ query, isRaw }> { }
// Add global search
override async getGlobalSearchQuery(query, globalSearch, user): Promise<{ query, isRaw }> { }
// Add sort order
override async getSortQuery(query, sort, user): Promise<{ query, isRaw }> { }
// Add extra query conditions (e.g., company filtering)
override async getExtraManipulateQuery(query, dto, user): Promise<{ query, isRaw }> { }
// Before insert hook
override async beforeInsertOperation(entity, user, queryRunner): Promise<void> { }
// After insert hook
override async afterInsertOperation(entity, user, queryRunner): Promise<void> { }
// Before update hook
override async beforeUpdateOperation(oldEntity, newEntity, user, queryRunner): Promise<void> { }
// After update hook
override async afterUpdateOperation(entity, user, queryRunner): Promise<void> { }
// Before delete hook
override async beforeDeleteOperation(dto, user, queryRunner): Promise<void> { }
// After delete hook
override async afterDeleteOperation(dto, user, queryRunner): Promise<void> { }
}RequestScopedApiService
For dynamic entity resolution based on runtime configuration (e.g., company feature).
import { RequestScopedApiService } from '@flusys/nestjs-shared/classes';
import { Injectable, Scope, Inject } from '@nestjs/common';
import { EntityTarget, Repository } from 'typeorm';
@Injectable({ scope: Scope.REQUEST })
export class RoleService extends RequestScopedApiService<
CreateRoleDto,
UpdateRoleDto,
IRole,
RoleBase,
Repository<RoleBase>
> {
constructor(
@Inject('CACHE_INSTANCE') protected override cacheManager: HybridCache,
@Inject(UtilsService) protected override utilsService: UtilsService,
@Inject(ModuleConfigService) private readonly config: ModuleConfigService,
@Inject(DataSourceProvider) private readonly provider: DataSourceProvider,
) {
// Pass null for repository - will be initialized dynamically
super('role', null as any, cacheManager, utilsService, 'RoleService', true);
}
// Required: Resolve which entity to use
protected resolveEntity(): EntityTarget<RoleBase> {
return this.config.isCompanyFeatureEnabled() ? RoleWithCompany : Role;
}
// Required: Return the DataSource provider
protected getDataSourceProvider(): IDataSourceProvider {
return this.provider;
}
// Optional: Initialize additional repositories
protected async initializeAdditionalRepositories(): Promise<void> {
this.actionRepository = await this.dataSourceProvider.getRepository(Action);
}
}Key Methods
| Method | Description |
|--------|-------------|
| ensureRepositoryInitialized() | Must call before using repository |
| resolveEntity() | Abstract - return entity class based on config |
| getDataSourceProvider() | Abstract - return datasource provider |
| getDataSourceForService() | Get raw DataSource for transactions |
ApiController - Generic CRUD Controller
The createApiController factory creates standardized POST-only RPC controllers.
Basic Usage
import { createApiController } from '@flusys/nestjs-shared/classes';
import { Controller, Inject } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('Users')
@Controller('users')
export class UserController extends createApiController<
CreateUserDto,
UpdateUserDto,
UserResponseDto,
IUser,
UserService
>(CreateUserDto, UpdateUserDto, UserResponseDto) {
constructor(@Inject(UserService) protected service: UserService) {
super(service);
}
}Generated Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| /insert | POST | Create entity |
| /insert-many | POST | Create multiple entities |
| /get/:id | POST | Get entity by ID |
| /get-all | POST | Get paginated list |
| /update | POST | Update entity |
| /update-many | POST | Update multiple entities |
| /delete | POST | Delete entity |
Security Configuration
// Global security (all endpoints)
@Controller('users')
export class UserController extends createApiController(
CreateUserDto,
UpdateUserDto,
UserResponseDto,
{ security: 'jwt' }, // All endpoints require JWT
) {}
// Per-endpoint security
@Controller('users')
export class UserController extends createApiController(
CreateUserDto,
UpdateUserDto,
UserResponseDto,
{
security: {
getAll: 'public', // No auth required
getById: 'jwt', // JWT required
insert: { level: 'permission', permissions: ['users.create'] },
update: { level: 'permission', permissions: ['users.update'] },
delete: { level: 'permission', permissions: ['users.delete'] },
},
},
) {}Decorators
@CurrentUser
Extract the logged-in user from request:
import { CurrentUser } from '@flusys/nestjs-shared/decorators';
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
@Controller('profile')
export class ProfileController {
@Post('me')
getProfile(@CurrentUser() user: ILoggedUserInfo) {
return { userId: user.id, companyId: user.companyId };
}
// Extract specific property
@Post('id')
getUserId(@CurrentUser('id') userId: string) {
return { userId };
}
}@Public
Mark route as public (skip authentication). Use sparingly - security risk.
import { Public } from '@flusys/nestjs-shared/decorators';
@Controller('auth')
export class AuthController {
@Public()
@Post('login')
login() { }
}@RequirePermission
Require specific permission(s) - AND logic by default:
import { RequirePermission } from '@flusys/nestjs-shared/decorators';
@Controller('admin')
export class AdminController {
// Requires 'admin.dashboard' permission
@RequirePermission('admin.dashboard')
@Post('dashboard')
getDashboard() { }
// Requires BOTH permissions
@RequirePermission('users.read', 'admin.access')
@Post('users')
getUsers() { }
}@RequireAnyPermission
Require any of the listed permissions - OR logic:
import { RequireAnyPermission } from '@flusys/nestjs-shared/decorators';
@Controller('reports')
export class ReportsController {
// Requires 'reports.view' OR 'reports.admin'
@RequireAnyPermission('reports.view', 'reports.admin')
@Post()
getReports() { }
}@RequirePermissionCondition
Build complex permission conditions with nested AND/OR logic:
import { RequirePermissionCondition } from '@flusys/nestjs-shared/decorators';
@Controller('sensitive')
export class SensitiveController {
// Complex: User needs 'users.read' AND ('admin' OR 'manager')
@RequirePermissionCondition({
operator: 'and',
permissions: ['users.read'],
children: [
{ operator: 'or', permissions: ['admin', 'manager'] }
]
})
@Post('complex')
getComplexData() { }
}@SanitizeHtml / @SanitizeAndTrim
Escape HTML entities for XSS prevention:
import { SanitizeHtml, SanitizeAndTrim } from '@flusys/nestjs-shared/decorators';
export class CreateCommentDto {
@SanitizeHtml()
@IsString()
content: string;
@SanitizeAndTrim() // Escapes HTML AND trims whitespace
@IsString()
title: string;
}@ApiResponseDto
Generates Swagger schema for response:
import { ApiResponseDto } from '@flusys/nestjs-shared/decorators';
@Controller('users')
export class UserController {
@Post('get-all')
@ApiResponseDto(UserResponseDto, true, 'list') // Array with PaginationMetaDto
getAll() { }
@Post('insert')
@ApiResponseDto(UserResponseDto, false) // Single item
insert() { }
}Guards
JwtAuthGuard
Validates JWT tokens for protected routes. Extends Passport's AuthGuard('jwt') and respects @Public() decorator.
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
// Apply globally in main.ts
@Module({
providers: [{ provide: APP_GUARD, useClass: JwtAuthGuard }],
})
export class AppModule {}Important: Constructor needs @Inject(Reflector) for bundled code.
PermissionGuard
Checks user permissions from cache with AND/OR/nested logic support.
import { PermissionGuard } from '@flusys/nestjs-shared/guards';
@Module({
providers: [
{ provide: APP_GUARD, useClass: PermissionGuard },
{
provide: 'PERMISSION_GUARD_CONFIG',
useValue: {
enableCompanyFeature: true,
userPermissionKeyFormat: 'permissions:user:{userId}',
companyPermissionKeyFormat: 'permissions:company:{companyId}:branch:{branchId}:user:{userId}',
},
},
],
})
export class AppModule {}Cache Key Formats:
// Without company feature
`permissions:user:{userId}`
// With company feature
`permissions:company:{companyId}:branch:{branchId}:user:{userId}`Permission Exceptions
import {
InsufficientPermissionsException, // 403 - Missing permissions
NoPermissionsFoundException, // 403 - No permissions in cache
PermissionSystemUnavailableException, // 500 - Cache unavailable
} from '@flusys/nestjs-shared/exceptions';Middleware
LoggerMiddleware
Combined middleware for request correlation and HTTP logging.
Features:
- Request ID generation/tracking (UUID or from
x-request-idheader) - Tenant ID tracking (from
x-tenant-idheader) - AsyncLocalStorage context for thread-safe access
- Automatic sensitive header redaction (authorization, cookie, x-api-key)
- Performance monitoring (warns on requests > 3s)
- Body truncation (max 1000 chars)
Usage:
import { LoggerMiddleware } from '@flusys/nestjs-shared/middlewares';
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}Accessing Request Context:
import {
getRequestId,
getTenantId,
getUserId,
getCompanyId,
setUserId,
setCompanyId,
} from '@flusys/nestjs-shared/middlewares';
@Injectable()
export class MyService {
async doSomething() {
const requestId = getRequestId();
const tenantId = getTenantId();
setUserId('user-123');
}
}Interceptors
ResponseMetaInterceptor
Adds _meta to all responses:
// Response includes:
{
"data": [...],
"_meta": {
"requestId": "abc-123",
"timestamp": "2024-01-01T00:00:00.000Z",
"responseTime": 45
}
}IdempotencyInterceptor
Prevents duplicate POST requests using X-Idempotency-Key header. Caches responses for 24 hours.
SetCreatedByOnBody / SetUpdateByOnBody / SetDeletedByOnBody
Auto-set audit user IDs on request body from authenticated user.
DeleteEmptyIdFromBodyInterceptor
Removes empty id fields from request body (single and array bodies).
QueryPerformanceInterceptor
Monitors execution time and warns if request > 1000ms (configurable).
Slug Interceptor
Auto-generates slug from name field using UtilsService.transformToSlug().
Caching System
HybridCache
Two-tier caching with in-memory (L1) and Redis (L2):
import { HybridCache } from '@flusys/nestjs-shared/classes';
@Injectable()
export class MyService {
constructor(@Inject('CACHE_INSTANCE') private cache: HybridCache) {}
async getData(key: string) {
// Check cache first
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.del(key);
}
async invalidateAll() {
await this.cache.reset(); // Clear L1
await this.cache.resetL2(); // Clear L2 (Redis)
}
}CacheModule Setup
import { CacheModule } from '@flusys/nestjs-shared/modules';
@Module({
imports: [
CacheModule.forRoot(
true, // isGlobal
60_000, // memoryTtl (ms)
5000 // memorySize (LRU max items)
),
],
})
export class AppModule {}Configuration via USE_CACHE_LABEL env:
'memory'- L1 only'redis'- L2 only'hybrid'- Both (default)
Multi-Tenant DataSource
Dynamic database connection management with connection pooling.
Setup
import { DataSourceModule } from '@flusys/nestjs-shared/modules';
@Module({
imports: [
DataSourceModule.forRoot({
bootstrapAppConfig: { databaseMode: 'multi-tenant' },
defaultDatabaseConfig: {
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
},
tenants: [
{ id: 'tenant1', database: 'tenant1_db' },
{ id: 'tenant2', database: 'tenant2_db', host: 'other-server.com' },
],
}),
],
})
export class AppModule {}MultiTenantDataSourceService
| Method | Description |
|--------|-------------|
| getDataSource() | Get DataSource for current tenant (from header) |
| getDataSourceForTenant(id) | Get DataSource for specific tenant |
| getRepository(entity) | Get repository for current tenant |
| withTenant(id, callback) | Execute callback with specific tenant |
| forAllTenants(callback) | Execute callback for all tenants |
| registerTenant(config) | Register new tenant at runtime |
| removeTenant(id) | Remove tenant and close connection |
| getCurrentTenantId() | Get tenant ID from request header |
DTOs
FilterAndPaginationDto
import { FilterAndPaginationDto } from '@flusys/nestjs-shared/dtos';
// Request body
{
"filter": { "status": "active" },
"pagination": { "currentPage": 0, "pageSize": 10 },
"sort": { "createdAt": "DESC" },
"select": ["id", "name", "email"],
"withDeleted": false
}DeleteDto
import { DeleteDto } from '@flusys/nestjs-shared/dtos';
// Soft delete
{ "id": "uuid", "type": "delete" }
// Restore
{ "id": "uuid", "type": "restore" }
// Permanent delete
{ "id": "uuid", "type": "permanent" }
// Multiple IDs
{ "id": ["uuid1", "uuid2"], "type": "delete" }Response DTOs
// Single item
class SingleResponseDto<T> {
success: boolean;
message: string;
data?: T;
_meta?: RequestMetaDto;
}
// Paginated list
class ListResponseDto<T> {
success: boolean;
message: string;
data?: T[];
meta: PaginationMetaDto; // { total, page, pageSize, count, hasMore?, totalPages? }
_meta?: RequestMetaDto;
}
// Bulk operations
class BulkResponseDto<T> {
success: boolean;
message: string;
data?: T[];
meta: BulkMetaDto; // { count, failed?, total? }
_meta?: RequestMetaDto;
}
// Message only
class MessageResponseDto {
success: boolean;
message: string;
_meta?: RequestMetaDto;
}Base Entities
Identity Entity
Base entity with UUID and timestamps:
import { Identity } from '@flusys/nestjs-shared/entities';
@Entity()
export class Product extends Identity {
// Inherited: id, createdAt, updatedAt, deletedAt
// Inherited: createdById, updatedById, deletedById
@Column()
name: string;
}UserRoot Entity
Base user entity with common fields:
import { UserRoot } from '@flusys/nestjs-shared/entities';
@Entity()
export class User extends UserRoot {
// Inherited: id, name, email, phone, profilePictureId
// Inherited: isActive, emailVerified, phoneVerified
// Inherited: lastLoginAt, additionalFields
// Inherited: createdAt, updatedAt, deletedAt
}Utilities
Query Helpers
import {
applyCompanyFilter,
buildCompanyWhereCondition,
hasCompanyId,
validateCompanyOwnership,
} from '@flusys/nestjs-shared/utils';
// Add company filter to TypeORM query
applyCompanyFilter(query, {
isCompanyFeatureEnabled: true,
entityAlias: 'entity',
}, user);
// Build where condition for company
const where = buildCompanyWhereCondition(baseWhere, user, isCompanyFeatureEnabled);
// Validate entity belongs to user's company
validateCompanyOwnership(entity, user, 'Entity');String Utilities
import { generateSlug, generateUniqueSlug } from '@flusys/nestjs-shared/utils';
// Generate URL-friendly slug
const slug = generateSlug('My Product Name', 100);
// Returns: 'my-product-name'
// Generate unique slug with collision detection
const uniqueSlug = await generateUniqueSlug(
'My Product',
async (pattern) => existingSlugs.filter(s => s.startsWith(pattern)),
100
);Request Utilities
import {
isBrowserRequest,
buildCookieOptions,
parseDurationToMs,
} from '@flusys/nestjs-shared/utils';
// Detect browser vs API client
const isBrowser = isBrowserRequest(req);
// Build secure cookie options
const cookieOpts = buildCookieOptions(req);
// Parse duration string
const ms = parseDurationToMs('7d'); // 604800000HTML Sanitizer
import { escapeHtml, escapeHtmlVariables } from '@flusys/nestjs-shared/utils';
// Escape single string
const safe = escapeHtml('<script>alert("xss")</script>');
// Returns: '<script>alert("xss")</script>'
// Escape all values in an object
const safeVars = escapeHtmlVariables({
userName: '<script>evil</script>',
message: 'Hello, World!',
});Error Handling
Error Handler Utilities
import {
getErrorMessage,
logError,
rethrowError,
logAndRethrow,
} from '@flusys/nestjs-shared/utils';
interface IErrorContext {
operation?: string;
entity?: string;
userId?: string;
id?: string;
companyId?: string;
data?: Record<string, unknown>;
}
// Safe error message extraction
const message = getErrorMessage(error);
// Log with sensitive key redaction (password, secret, token, apiKey)
logError(logger, error, 'createUser', { userId: user.id });
// Type-safe rethrow
rethrowError(error);
// Combined log + rethrow
logAndRethrow(logger, error, 'updateUser', context);Constants
Permission Constants
import { PERMISSIONS } from '@flusys/nestjs-shared/constants';
// Organized by module:
PERMISSIONS.USER.CREATE // 'user.create'
PERMISSIONS.USER.READ // 'user.read'
PERMISSIONS.USER.UPDATE // 'user.update'
PERMISSIONS.USER.DELETE // 'user.delete'
PERMISSIONS.COMPANY.CREATE // 'company.create'
PERMISSIONS.BRANCH.CREATE // 'branch.create'
PERMISSIONS.ROLE.CREATE // 'role.create'
PERMISSIONS.FILE.CREATE // 'file.create'
PERMISSIONS.FOLDER.CREATE // 'folder.create'
// ...etcInjection Tokens
'CACHE_INSTANCE' // HybridCache provider
'PERMISSION_GUARD_CONFIG' // PermissionGuard config
'LOGGER_INSTANCE' // Logger providerAPI Reference
Main Exports
// Classes
import {
ApiService,
RequestScopedApiService,
createApiController,
HybridCache,
WinstonLoggerAdapter,
NestLoggerAdapter,
} from '@flusys/nestjs-shared/classes';
// Decorators
import {
CurrentUser,
Public,
RequirePermission,
RequireAnyPermission,
RequirePermissionCondition,
SanitizeHtml,
SanitizeAndTrim,
ApiResponseDto,
} from '@flusys/nestjs-shared/decorators';
// Guards
import { JwtAuthGuard, PermissionGuard } from '@flusys/nestjs-shared/guards';
// Interceptors
import {
ResponseMetaInterceptor,
IdempotencyInterceptor,
SetCreatedByOnBody,
SetUpdateByOnBody,
SetDeletedByOnBody,
DeleteEmptyIdFromBodyInterceptor,
QueryPerformanceInterceptor,
Slug,
} from '@flusys/nestjs-shared/interceptors';
// Modules
import {
CacheModule,
DataSourceModule,
UtilsModule,
UtilsService,
MultiTenantDataSourceService,
} from '@flusys/nestjs-shared/modules';
// DTOs
import {
FilterAndPaginationDto,
PaginationDto,
DeleteDto,
GetByIdBodyDto,
SingleResponseDto,
ListResponseDto,
BulkResponseDto,
MessageResponseDto,
IdentityResponseDto,
PaginationMetaDto,
BulkMetaDto,
RequestMetaDto,
} from '@flusys/nestjs-shared/dtos';
// Entities
import { Identity, UserRoot } from '@flusys/nestjs-shared/entities';
// Interfaces
import {
ILoggedUserInfo,
IService,
IDataSourceProvider,
IModuleConfigService,
ILogger,
PermissionCondition,
PermissionOperator,
} from '@flusys/nestjs-shared/interfaces';
// Utilities
import {
getErrorMessage,
logError,
applyCompanyFilter,
buildCompanyWhereCondition,
validateCompanyOwnership,
generateSlug,
generateUniqueSlug,
isBrowserRequest,
buildCookieOptions,
parseDurationToMs,
escapeHtml,
escapeHtmlVariables,
} from '@flusys/nestjs-shared/utils';
// Middleware
import {
LoggerMiddleware,
getRequestId,
getTenantId,
getUserId,
getCompanyId,
setUserId,
setCompanyId,
} from '@flusys/nestjs-shared/middlewares';
// Exceptions
import {
InsufficientPermissionsException,
NoPermissionsFoundException,
PermissionSystemUnavailableException,
} from '@flusys/nestjs-shared/exceptions';
// Constants
import { PERMISSIONS } from '@flusys/nestjs-shared/constants';Best Practices
1. Use Generic Service Pattern
// Extend ApiService for consistent CRUD
@Injectable()
export class ProductService extends ApiService<...> {
// Override hooks for customization
}
// For dynamic entities, use RequestScopedApiService
@Injectable({ scope: Scope.REQUEST })
export class RoleService extends RequestScopedApiService<...> { }2. Always Use @Inject() Decorators
Required for esbuild bundled code:
// CORRECT
constructor(
@Inject(MyService) private readonly myService: MyService,
@Inject('CACHE_INSTANCE') private readonly cache: HybridCache,
) {}
// WRONG - fails in bundled code
constructor(private readonly myService: MyService) {}3. Use Decorators Consistently
// Use built-in decorators
@CurrentUser() user: ILoggedUserInfo
@RequirePermission('users.create')
@Public() // Use sparingly!
// Don't access request directly
@Req() req: Request // Not type-safe4. Configure Security at Controller Level
// GOOD - configure in createApiController
export class UserController extends createApiController(..., {
security: { insert: 'permission', ... },
}) {}
// AVOID - adding guards to each endpoint
@UseGuards(JwtGuard)
@Post('create')
create() {}Last Updated: 2026-02-21
