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

@flusys/nestjs-shared

v1.1.0

Published

Common shared utilities for Flusys NestJS applications

Readme

Shared Package Guide

Package: @flusys/nestjs-shared Version: 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

@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 patterns

Package 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.ts

ApiService - 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-id header)
  • Tenant ID tracking (from x-tenant-id header)
  • 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');  // 604800000

HTML Sanitizer

import { escapeHtml, escapeHtmlVariables } from '@flusys/nestjs-shared/utils';

// Escape single string
const safe = escapeHtml('<script>alert("xss")</script>');
// Returns: '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'

// 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'
// ...etc

Injection Tokens

'CACHE_INSTANCE'           // HybridCache provider
'PERMISSION_GUARD_CONFIG'  // PermissionGuard config
'LOGGER_INSTANCE'          // Logger provider

API 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-safe

4. 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