npm package: @sftech/nestjs-core
Description: Foundation library providing base abstractions, utilities, and common functionality for all NestJS applications in the sftech ecosystem.
Latest version: 1.1.0

# @sftech/nestjs-core

Foundation library providing base abstractions, utilities, and common functionality for all NestJS applications in the sftech ecosystem.

## Installation

```bash
npm install @sftech/nestjs-core
```

## Features

- Base use case interface for Clean Architecture
- Factory pattern interface
- Global exception handling with custom exceptions
- Application bootstrap utilities
- Configurable CORS origins support
- Configuration management with validation
- Status/health check module
- Request context storage for async operations

## Configuration

### Basic Setup

```typescript
import { NestjsCoreModule } from '@sftech/nestjs-core';

@Module({
  imports: [NestjsCoreModule],
})
export class AppModule {}
```

### With Configuration

```typescript
import { NestjsCoreModule, Configuration } from '@sftech/nestjs-core';
import { join } from 'path';

// Load configuration before module initialization
Configuration.load(join(__dirname, 'assets', 'app.config.json'));

@Module({
  imports: [NestjsCoreModule.register({ statusRoute: true })],
  providers: [Logger],
})
export class AppModule {}
```

### Bootstrap Application

```typescript
// main.ts
import { AppBootstrapper, Configuration } from '@sftech/nestjs-core';
import { AppModule } from './app/app.module';
import { join } from 'path';

// Load configuration first
Configuration.load(join(__dirname, 'assets', 'app.config.json'));

// Bootstrap with full options
AppBootstrapper.run(AppModule, {
  appName: 'MyApp',
  logging: {
    filePath: './logs/app.log',
    enableDebugLogs: false,
  },
  swagger: {
    title: 'My API',
    description: 'API documentation',
    version: '1.0.0',
  },
});
```

**Required `app.config.json`:**
```json
{
  "APP_PORT": 3000,
  "APP_HOST": "0.0.0.0",
  "APP_API_PREFIX": "/api"
}
```

## API Reference

### Interfaces

| Interface | Purpose |
|-----------|---------|
| `IBaseUsecase<TInput, TOutput>` | Base interface for all use cases |
| `IFactory<TReturn>` | Factory pattern interface |
| `IBootstrapConfig` | Application bootstrap configuration |
| `IBootstrapCorsConfig` | CORS configuration for `AppBootstrapper` (added in v1.1.0) |
| `IConfigurationValues` | Configuration key-value storage |

### Exceptions

| Exception | Purpose |
|-----------|---------|
| `BaseApplicationException` | Base class for all custom exceptions |
| `ConfigurationNotFoundException` | Thrown when configuration file not found |
| `ConfigurationKeyNotFoundException` | Thrown when configuration key missing |
| `ConfigurationNotValidJsonException` | Thrown when configuration is invalid JSON |

### Utilities

| Utility | Purpose |
|---------|---------|
| `AppBootstrapper.run()` | Bootstrap NestJS application with standard config |
| `GlobalExceptionFilter` | Global exception handler for HTTP responses |
| `RequestContextStorage` | Store request context for async operations |
| `Configuration` | Static configuration management |

---

## Base Classes & Interfaces

### IBaseUsecase<TInput, TOutput>

Base interface for implementing use cases following Clean Architecture. **Every use case in your application should implement this interface.**

**Interface definition:**
```typescript
export interface IBaseUsecase<TInput, TOutput> {
    execute(input: TInput): TOutput;
}
```

**You must implement:**
- `execute(input: TInput): TOutput` - The use case logic

**Usage example:**
```typescript
import { IBaseUsecase } from '@sftech/nestjs-core';
import { Injectable } from '@nestjs/common';

// Define input/output types
interface GetUserInput {
    id: number;
}

interface GetUserOutput {
    id: number;
    email: string;
    name: string;
}

// Implement the use case
@Injectable()
export class GetUserUsecase implements IBaseUsecase<GetUserInput, Promise<GetUserOutput>> {
    constructor(private readonly userRepository: IUserRepository) {}

    async execute(input: GetUserInput): Promise<GetUserOutput> {
        const user = await this.userRepository.findById(input.id);
        if (!user) {
            throw new UserNotFoundException(input.id);
        }
        return {
            id: user.id,
            email: user.email,
            name: user.name,
        };
    }
}

// Register in module
@Module({
    providers: [GetUserUsecase],
    exports: [GetUserUsecase],
})
export class UserModule {}

// Use in controller
@Controller('users')
export class UserController {
    constructor(private readonly getUserUsecase: GetUserUsecase) {}

    @Get(':id')
    async getUser(@Param('id') id: number) {
        return this.getUserUsecase.execute({ id });
    }
}
```

---

### IFactory<TReturn>

Factory pattern interface for creating objects.

**Interface definition:**
```typescript
export interface IFactory<TReturn> {
    create(): TReturn;
}
```

**You must implement:**
- `create(): TReturn` - Create and return a new instance

**Usage example:**
```typescript
import { IFactory } from '@sftech/nestjs-core';
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserFactory implements IFactory<User> {
    create(): User {
        const user = new User();
        user.createdAt = new Date();
        user.isActive = true;
        return user;
    }
}

// Usage
@Injectable()
export class CreateUserUsecase {
    constructor(private readonly userFactory: UserFactory) {}

    execute(dto: CreateUserDto): User {
        const user = this.userFactory.create();
        user.email = dto.email;
        user.name = dto.name;
        return user;
    }
}
```

---

### BaseApplicationException

Base class for all custom application exceptions. **Extend this class to create domain-specific exceptions.**

**Provides:**
- `message: string` - Error message
- Inherits from `Error`

**Usage example:**
```typescript
import { BaseApplicationException } from '@sftech/nestjs-core';

// Create a custom exception
export class UserNotFoundException extends BaseApplicationException {
    constructor(userId: number) {
        super(`User with ID ${userId} not found`);
        this.name = 'UserNotFoundException';
    }
}

export class InvalidEmailException extends BaseApplicationException {
    constructor(email: string) {
        super(`Email '${email}' is not valid`);
        this.name = 'InvalidEmailException';
    }
}

// Usage in a service
@Injectable()
export class UserService {
    async findById(id: number): Promise<User> {
        const user = await this.repository.findById(id);
        if (!user) {
            throw new UserNotFoundException(id);
        }
        return user;
    }
}
```

---

### RequestContextStorage

Storage for request-scoped context, useful for passing user information through the application layers without explicit parameter passing.

**Key methods:**
- `set(key: string, value: any)` - Store a value
- `get<T>(key: string): T` - Retrieve a value

**Usage example:**
```typescript
import { RequestContextStorage } from '@sftech/nestjs-core';
import { Injectable } from '@nestjs/common';

// Middleware to set context
@Injectable()
export class AuthMiddleware implements NestMiddleware {
    constructor(private readonly rcs: RequestContextStorage) {}

    use(req: Request, res: Response, next: NextFunction) {
        const user = this.validateToken(req.headers.authorization);
        this.rcs.set('user', user);
        this.rcs.set('requestId', req.headers['x-request-id']);
        next();
    }
}

// Use in any service
@Injectable()
export class AuditService {
    constructor(private readonly rcs: RequestContextStorage) {}

    logAction(action: string) {
        const user = this.rcs.get<User>('user');
        const requestId = this.rcs.get<string>('requestId');
        console.log(`[${requestId}] User ${user?.id} performed: ${action}`);
    }
}
```

---

### Configuration

Static configuration management for loading and accessing application configuration. Supports both JSON file configuration and environment variable overrides.

**Key methods:**
- `Configuration.load(path: string)` - Load configuration from JSON file
- `Configuration.getValue(key: string)` - Get configuration value (env vars take precedence)
- `Configuration.getConfig()` - Get entire configuration object

**Environment variable override:** If an environment variable with the same name as a config key exists, the env var value takes precedence.

**Usage example:**
```typescript
import { Configuration } from '@sftech/nestjs-core';
import { join } from 'path';

// In main.ts or app.module.ts - load once at startup
Configuration.load(join(__dirname, 'assets', 'app.config.json'));

// app.config.json
{
  "APP_PORT": 3000,
  "APP_HOST": "localhost",
  "APP_API_PREFIX": "/api",
  "DATABASE_HOST": "localhost",
  "DATABASE_PORT": 5432,
  "FEATURE_FLAG_NEW_UI": true
}

// Access anywhere in the application
@Injectable()
export class DatabaseService {
    private readonly host: string;
    private readonly port: number;

    constructor() {
        // getValue throws ConfigurationKeyNotFoundException if key not found
        this.host = Configuration.getValue('DATABASE_HOST') as string;
        this.port = Configuration.getValue('DATABASE_PORT') as number;
    }
}

// Environment variables override config file values
// If DATABASE_HOST env var is set, it will be used instead of config file value
```

---

### NestjsCoreModule

The main module that provides core functionality. Use `register()` to configure.

**Configuration options:**
- `statusRoute: boolean` - Enable `/status` health check endpoint

**Usage example:**
```typescript
import { NestjsCoreModule, Configuration } from '@sftech/nestjs-core';
import { Module } from '@nestjs/common';
import { join } from 'path';

// Load configuration BEFORE module initialization
Configuration.load(join(__dirname, 'assets', 'app.config.json'));

@Module({
  imports: [
    NestjsCoreModule.register({
      statusRoute: true,  // Enables GET /status endpoint
    }),
  ],
})
export class AppModule {}
```

**What it provides:**
- `RequestContextStorage` - Automatically available for injection
- `RequestContextMiddleware` - Applied to all routes automatically
- `StatusModule` - Health check endpoint (if enabled)

---

### AppBootstrapper

Bootstrap utility for starting NestJS applications with standardized configuration including logging, Swagger, validation, and CORS.

**Method:**
- `AppBootstrapper.run(module, config)` - Bootstrap and start the application

**What it configures automatically:**
- Winston logging with console and file transports
- Global validation pipe with whitelist and transform
- Cookie parser middleware
- CORS (see CORS configuration below)
- Global exception filter
- Swagger documentation (optional)

**Configuration interfaces:**

```typescript
interface IBootstrapConfig {
    appName?: string;              // Application name for logging
    globalPrefix?: string;         // API prefix (overridden by APP_API_PREFIX config)
    logging?: IBootstrapLoggingConfig;
    swagger?: IBootstrapSwaggerConfig;
    useBodyParser?: boolean;       // Enable body parser (default: true, limit: 10mb)
    cors?: IBootstrapCorsConfig;   // CORS configuration (added in v1.1.0)
}

interface IBootstrapLoggingConfig {
    filePath?: string;             // Path to log file (e.g., './logs/app.log')
    enableDebugLogs?: boolean;     // Enable debug level logging
}

interface IBootstrapSwaggerConfig {
    title?: string;                // Swagger doc title
    description?: string;          // Swagger doc description
    version?: string;              // API version
    path?: string;                 // Swagger UI path (default: 'api/swagger')
}

interface IBootstrapCorsConfig {
    originsKey?: string;           // Config key for pipe-separated origins (default: 'CORS_ORIGINS')
}
```

**Required configuration keys (in app.config.json):**
- `APP_PORT` - Port to listen on
- `APP_HOST` - Host to bind to
- `APP_API_PREFIX` - Global API prefix

**CORS behavior:**

| Scenario | Result |
|----------|--------|
| No `cors` property in `IBootstrapConfig` | `enableCors()` called without parameters (wildcard `*`) |
| `cors: {}` but `CORS_ORIGINS` key is absent or empty | `enableCors()` called without parameters (wildcard fallback) |
| `cors: {}` and `CORS_ORIGINS` has a value | Specific origins enforced, `credentials: true` set |
| `cors: { originsKey: 'MY_KEY' }` and `MY_KEY` has a value | Origins read from `MY_KEY` instead of `CORS_ORIGINS` |

When specific origins are configured the bootstrapper sets:
- `credentials: true` - Supports `withCredentials: true` from browsers
- `methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS'`
- `allowedHeaders: 'Origin,X-Requested-With,Content-Type,Accept,Authorization'`
- Requests without an `Origin` header (server-to-server calls) are always allowed

**Complete usage example:**

```typescript
// main.ts
import { AppBootstrapper, Configuration } from '@sftech/nestjs-core';
import { AppModule } from './app/app.module';
import { join } from 'path';

// 1. Load configuration first
Configuration.load(join(__dirname, 'assets', 'app.config.json'));

// 2. Bootstrap application
AppBootstrapper.run(AppModule, {
    appName: 'MyApp',
    logging: {
        filePath: './logs/app.log',
        enableDebugLogs: process.env.NODE_ENV === 'development',
    },
    swagger: {
        title: 'My API',
        description: 'REST API documentation',
        version: '1.0.0',
        path: 'api/docs',  // Swagger UI at /api/docs
    },
});

// app.config.json
{
    "APP_PORT": 3000,
    "APP_HOST": "0.0.0.0",
    "APP_API_PREFIX": "/api"
}
```

#### CORS Examples

**Default behavior (wildcard, backward compatible) - no `cors` property:**

```typescript
// main.ts
AppBootstrapper.run(AppModule, {
    appName: 'MyApp',
    // No cors property: Access-Control-Allow-Origin: * (same as before v1.1.0)
});
```

**Configuring specific allowed origins via `app.config.json`:**

```typescript
// main.ts
AppBootstrapper.run(AppModule, {
    appName: 'MyApp',
    cors: {},  // Activates CORS config; reads 'CORS_ORIGINS' from app.config.json
});

// app.config.json
{
    "APP_PORT": 3000,
    "APP_HOST": "0.0.0.0",
    "APP_API_PREFIX": "/api",
    "CORS_ORIGINS": "https://app.example.com|http://localhost:4200"
}

// Result: only https://app.example.com and http://localhost:4200 are allowed,
// with Access-Control-Allow-Credentials: true
```

**Using a custom configuration key:**

```typescript
// main.ts
AppBootstrapper.run(AppModule, {
    appName: 'MyApp',
    cors: {
        originsKey: 'ALLOWED_ORIGINS',  // Read from ALLOWED_ORIGINS instead of CORS_ORIGINS
    },
});

// app.config.json
{
    "APP_PORT": 3000,
    "APP_HOST": "0.0.0.0",
    "APP_API_PREFIX": "/api",
    "ALLOWED_ORIGINS": "https://app.example.com|capacitor://localhost"
}
```

**Origin format notes:**
- Multiple origins are separated by pipe (`|`): `"https://a.com|https://b.com"`
- Whitespace around pipes is trimmed: `"https://a.com | https://b.com"` works correctly
- A single origin without a pipe is valid: `"https://app.example.com"`
- Capacitor/Ionic origins are supported as-is: `"capacitor://localhost"`
- If the key is absent, empty, or not found in config, behavior falls back to wildcard `*`

---

### Logging

Winston logging utilities for creating configured transports.

**Static methods:**
- `Logging.getWinstonConsoleTransport(appName)` - Console transport (info level)
- `Logging.getWinstonConsoleDebugTransport(appName)` - Console transport (debug level)
- `Logging.getWinstonFileTransport(appName, logPath)` - File transport

**Usage example (custom logging setup):**
```typescript
import { Logging } from '@sftech/nestjs-core';
import { WinstonModule } from 'nest-winston';

// Custom NestJS logger configuration
const app = await NestFactory.create(AppModule, {
    logger: WinstonModule.createLogger({
        transports: [
            Logging.getWinstonConsoleTransport('MyApp'),
            Logging.getWinstonFileTransport('MyApp', './logs/app.log'),
        ],
    }),
});
```

**Note:** When using `AppBootstrapper`, logging is configured automatically. Use these methods only for custom logging setups.

---

### GlobalExceptionFilter

Global exception filter that catches all exceptions and returns standardized HTTP responses.

**Automatically registered when using `NestjsCoreModule`.**

**Manual registration (if needed):**
```typescript
import { GlobalExceptionFilter } from '@sftech/nestjs-core';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    app.useGlobalFilters(new GlobalExceptionFilter());
    await app.listen(3000);
}
```

---

## Dependencies

**No peer dependencies** - This is the foundation library.

## Development

```bash
# Build
npx nx build nestjs-core

# Format
npx biome format --write libs/nestjs-core/
```

## Changelog

See [CHANGELOG.md](./CHANGELOG.md)

## License

MIT