No description
Find a file
Claude Code ab6bc0d12e
Some checks failed
Build and Publish (Auto-detect) / build-and-publish (push) Failing after 42s
deps-upgrade(deps): ⬆️ Update dependencies to maintain compatibility and security patches
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-04-02 02:31:49 -07:00
.forgejo/workflows chore: initial commit 2026-01-21 11:37:31 -08:00
src chore(core): 🔧 Update core dependency files (bootstrap.ts, index.ts) and related utility scripts 2026-02-18 14:33:04 -08:00
.gitignore chore: initial commit 2026-01-21 11:37:31 -08:00
eslint.config.js chore: initial commit 2026-01-21 11:37:31 -08:00
package.json deps-upgrade(deps): ⬆️ Update dependencies to maintain compatibility and security patches 2026-04-02 02:31:49 -07:00
README.md chore: trigger CI publish 2026-01-30 11:56:24 -08:00
tsconfig.json chore: initial commit 2026-01-21 11:37:31 -08:00
tsup.config.ts chore(config): 🔧 Update tsup build configuration for entry points, output format, and minification 2026-03-19 20:05:41 -07:00

@lilith/service-nestjs-bootstrap

NestJS application bootstrap factory with common configurations, module factories, and service presets.

Features

  • Bootstrap Factory: Create and configure NestJS apps with one function call
  • Automatic Dependency Startup: Idempotently start service dependencies before bootstrap
  • Security Middleware: Helmet, CORS, cookie-parser, CSRF protection
  • Validation Pipe: Strict or loose validation with sensible defaults
  • Swagger Integration: Auto-configured OpenAPI documentation
  • Service Presets: Pre-configured settings for APIs, microservices, and gateways
  • Module Factories: ConfigModule with sensible defaults

Installation

pnpm add @lilith/service-nestjs-bootstrap

Peer Dependencies

pnpm add @nestjs/common @nestjs/core @nestjs/platform-express @nestjs/swagger

Optional Dependencies

# For ConfigModule factory
pnpm add @nestjs/config

# For automatic dependency startup (v2.0+)
pnpm add @lilith/service-addresses

Quick Start

import { bootstrap, presets } from '@lilith/service-nestjs-bootstrap';
import { AppModule } from './app.module';

bootstrap(AppModule, {
  ...presets.api,
  swagger: { enabled: true, title: 'My API' },
  port: 3000,
});

Usage

Dynamic CORS Configuration (v2.0+)

Automatically generate CORS origins from the service registry, eliminating hardcoded localhost URLs.

getCorsOrigins()

Discovers all frontend services from ports.yaml via @lilith/service-addresses:

import { bootstrap, getCorsOrigins } from '@lilith/service-nestjs-bootstrap';

bootstrap(AppModule, {
  cors: {
    origins: getCorsOrigins({
      includeEnvironmentOrigins: true,
      additionalOrigins: ['https://production.example.com'],
      allowWildcard: process.env.NODE_ENV !== 'production',
    }),
    credentials: true,
  },
  port: 3000,
});

Options:

  • additionalOrigins?: string[] - Additional origins (e.g., production domains)
  • includeEnvironmentOrigins?: boolean - Include CORS_ORIGINS env var (default: true)
  • allowWildcard?: boolean - Allow wildcard * (default: false, dev only)
  • additionalServicePatterns?: string[] - Custom service name patterns

Service Discovery Patterns:

The utility matches these service name patterns:

  • frontend-dev - Vite dev servers
  • frontend-admin - Admin interfaces
  • frontend-public - Public-facing interfaces
  • web - Web servers

Example (from ports.yaml):

features:
  analytics:
    frontend-dev: 5173
  platform-admin:
    frontend-dev: 3200
  marketplace:
    frontend-dev: 5200

Generated origins:

[
  'http://localhost:5173',  // analytics
  'http://localhost:3200',  // platform-admin
  'http://localhost:5200',  // marketplace
  // ... all other discovered frontends
]

getCorsOriginsForFeatures()

Restricts CORS to specific features only:

import { getCorsOriginsForFeatures } from '@lilith/service-nestjs-bootstrap';

// Only allow analytics and platform-admin frontends
const origins = getCorsOriginsForFeatures(['analytics', 'platform-admin'], {
  additionalOrigins: ['https://api.example.com'],
  servicePatterns: ['frontend-dev', 'frontend-admin'],
});

bootstrap(AppModule, {
  cors: { origins, credentials: true },
});

Environment Variable:

# Comma-separated production domains (auto-included)
CORS_ORIGINS=https://api.lilith.gg,https://admin.lilith.gg

Migration from Hardcoded CORS:

- cors: {
-   origins: ['http://localhost:3000', 'http://localhost:5173'],
-   credentials: true,
- },
+ cors: {
+   origins: getCorsOrigins({ includeEnvironmentOrigins: true }),
+   credentials: true,
+ },

Benefits:

  • Single source of truth (ports.yaml)
  • Automatic discovery of new frontends
  • Type-safe (no hardcoded strings)
  • Production-ready (env var support)
  • Graceful degradation if registry unavailable

Basic Bootstrap

import { bootstrap } from '@lilith/service-nestjs-bootstrap';
import { AppModule } from './app.module';

// Quick start - creates and starts the app
await bootstrap(AppModule, {
  port: 3000,
  swagger: {
    enabled: true,
    title: 'My API',
    description: 'API Documentation',
    version: '1.0.0',
  },
});

Advanced Configuration

import { createNestApp } from '@lilith/service-nestjs-bootstrap';
import { AppModule } from './app.module';

const result = await createNestApp(AppModule, {
  port: 3000,
  helmet: true,
  cors: {
    origins: ['https://app.example.com'],
    credentials: true,
  },
  cookieParser: { secret: process.env.COOKIE_SECRET },
  validationPipe: 'strict',
  globalPrefix: 'api/v1',
  swagger: {
    enabled: true,
    title: 'My API',
    bearerAuth: true,
    tags: ['users', 'products'],
  },
  customConfig: async (app) => {
    // Add custom middleware, interceptors, etc.
  },
});

// Access the app before starting
console.log(`Will listen on ${result.url}`);

// Start listening
await result.listen();

Automatic Dependency Startup (v2.0+)

Idempotent service dependency management: Automatically start dependencies declared in services.yaml before bootstrapping your application. Already-running dependencies are detected and accepted.

import { bootstrap, presets } from '@lilith/service-nestjs-bootstrap';
import { AppModule } from './app.module';

await bootstrap(AppModule, {
  ...presets.api,
  dependencies: {
    feature: 'analytics',               // Feature name from services.yaml (required)
    autoStart: true,                    // Enable automatic dependency startup (default: true, set false to disable)
    waitForHealth: true,                // Wait for dependencies to be healthy (default: true)
    healthCheckTimeout: 60000,          // Health check timeout in ms (default: 60000)
    skipDependencies: ['analytics.redis'], // Optional: skip specific dependencies
    onProgress: (event) => {            // Optional: track startup progress
      console.log(`[${event.service}] ${event.phase}: ${event.message}`);
    },
  },
  port: 3012,
});

With explicit typing:

import {
  bootstrap,
  presets,
  type DependencyStartupEvent
} from '@lilith/service-nestjs-bootstrap';
import { AppModule } from './app.module';

const handleProgress = (event: DependencyStartupEvent) => {
  console.log(`[${event.service}] ${event.phase}: ${event.message}`);
};

await bootstrap(AppModule, {
  ...presets.api,
  dependencies: {
    feature: 'analytics',
    onProgress: handleProgress,
  },
  port: 3012,
});

Using the default progress logger:

import {
  bootstrap,
  presets,
  createDefaultProgressLogger
} from '@lilith/service-nestjs-bootstrap';
import { AppModule } from './app.module';

await bootstrap(AppModule, {
  ...presets.api,
  dependencies: {
    feature: 'analytics',
    onProgress: createDefaultProgressLogger('Analytics API'),
  },
  port: 3012,
});

How it works:

  1. Multi-strategy detection - Dependencies are checked via:

    • PID file (did we start it?)
    • HTTP health endpoint
    • Docker container status
    • TCP port availability
  2. Idempotent behavior:

    • Already running → Detected and accepted (continues immediately)
    • Not running → Started with health checks
    • Crashed (stale PID) → Detected and restarted
  3. Race condition safety - Lock files prevent simultaneous startup conflicts

Prerequisites:

# Required for dependency startup
pnpm add @lilith/service-addresses

Environment Variables:

Dependency startup respects these environment variables (all optional):

Variable Default Description
LILITH_SERVICES_PATH codebase/features Path to features directory containing services.yaml files
LILITH_PORTS_PATH infrastructure/ports.yaml Path to global ports configuration
LILITH_STRICT_VALIDATION false Enable strict port conflict validation (recommended: false for dev)

Example:

# Override default paths
export LILITH_SERVICES_PATH=./features
export LILITH_PORTS_PATH=./config/ports.yaml
export LILITH_STRICT_VALIDATION=false

# Start service
pnpm dev

Example: Analytics service with PostgreSQL + Redis dependencies

// codebase/features/analytics/backend-api/src/main.ts
import { bootstrap, presets } from '@lilith/service-nestjs-bootstrap';
import { AppModule } from './app.module';

async function main() {
  await bootstrap(AppModule, {
    ...presets.api,
    dependencies: {
      feature: 'analytics',  // Reads codebase/features/analytics/services.yaml
    },
    port: 3012,
    swagger: {
      enabled: process.env.NODE_ENV !== 'production',
      title: 'Analytics Service',
      version: '1.0',
      bearerAuth: true,
    },
  });
}

main();

Startup behavior:

# First run (nothing running)
[analytics.postgresql] checking: Checking service status...
[analytics.postgresql] starting: Starting PostgreSQL container...
[analytics.postgresql] waiting: Waiting for health check...
[analytics.postgresql] ready: Service is healthy (responseTime: 45ms)
[analytics.redis] checking: Checking service status...
[analytics.redis] starting: Starting Redis container...
[analytics.redis] waiting: Waiting for health check...
[analytics.redis] ready: Service is healthy (responseTime: 12ms)
✅ All dependencies for analytics are ready

# Second run (dependencies already running)
[analytics.postgresql] checking: Checking service status...
[analytics.postgresql] ready: Service already running (startedBy: other, source: health_check)
[analytics.redis] checking: Checking service status...
[analytics.redis] ready: Service already running (startedBy: other, source: docker)
✅ All dependencies for analytics are ready

Presets

API Preset

Standard REST API configuration:

import { bootstrap, presets } from '@lilith/service-nestjs-bootstrap';

bootstrap(AppModule, {
  ...presets.api,
  swagger: {
    ...presets.api.swagger,
    title: 'My API',
  },
  port: 3000,
});

Features:

  • Helmet security headers
  • CORS enabled for development
  • Cookie parser enabled
  • Strict validation pipe
  • Swagger enabled (non-production)
  • Shutdown hooks enabled

Microservice Preset

Minimal configuration for internal services:

import { bootstrap, presets } from '@lilith/service-nestjs-bootstrap';

bootstrap(AppModule, {
  ...presets.microservice,
  port: 3001,
});

Features:

  • Helmet security headers
  • No CORS (internal only)
  • No cookie parser
  • Strict validation
  • No Swagger
  • Fast startup

Gateway Preset

Full security for API gateways:

import { bootstrap, presets } from '@lilith/service-nestjs-bootstrap';

bootstrap(AppModule, {
  ...presets.gateway,
  swagger: {
    ...presets.gateway.swagger,
    title: 'Platform Gateway',
  },
  port: 4000,
});

Features:

  • Full Helmet with CSP configuration
  • CORS enabled
  • Cookie parser with secret
  • CSRF protection
  • Comprehensive Swagger

Module Factories

ConfigModule

import { createConfigModule } from '@lilith/service-nestjs-bootstrap';

@Module({
  imports: [
    createConfigModule({
      isGlobal: true,
      envFilePath: ['.env.local', '.env'],
      expandVariables: true,
    }),
  ],
})
export class AppModule {}

API Reference

BootstrapConfig

interface BootstrapConfig {
  // NestFactory options
  nestFactoryOptions?: NestApplicationOptions;

  // Security
  helmet?: boolean | HelmetOptions;          // Default: true
  cors?: CorsConfig | false;                 // Default: development CORS
  cookieParser?: boolean | { secret?: string }; // Default: true
  csrf?: boolean | { cookieName?: string; headerName?: string };

  // Validation
  validationPipe?: 'strict' | 'loose' | false; // Default: 'strict'

  // Swagger
  swagger?: SwaggerConfig;

  // Routing
  globalPrefix?: string;
  port?: number;                             // Default: 3000

  // Dependencies (v2.0+)
  dependencies?: DependencyStartupConfig;

  // Lifecycle
  enableShutdownHooks?: boolean;             // Default: true
  globalProviders?: Array<Type<unknown>>;
  customConfig?: (app: INestApplication) => void | Promise<void>;
}

DependencyStartupConfig

interface DependencyStartupConfig {
  feature: string;                           // Feature name from services.yaml (required)
  autoStart?: boolean;                       // Enable automatic dependency startup (default: true)
  waitForHealth?: boolean;                   // Default: true
  healthCheckTimeout?: number;               // Default: 60000 (60 seconds)
  skipDependencies?: string[];               // Dependencies to skip
  onProgress?: (event: DependencyStartupEvent) => void;
}

interface DependencyStartupEvent {
  phase: 'checking' | 'starting' | 'waiting' | 'ready' | 'error';
  service: string;                           // e.g., 'analytics.postgresql'
  message: string;
  metadata?: Record<string, unknown>;
}

SwaggerConfig

interface SwaggerConfig {
  enabled: boolean;
  path?: string;           // Default: 'api-docs'
  exportPath?: string;     // Export OpenAPI JSON
  title?: string;
  description?: string;
  version?: string;        // Default: '1.0'
  tags?: Array<string | { name: string; description?: string }>;
  bearerAuth?: boolean;
}

CorsConfig

interface CorsConfig {
  origins: string[];
  credentials?: boolean;   // Default: true
  methods?: string[];      // Default: all methods
  allowedHeaders?: string[];
  exposedHeaders?: string[];
  maxAge?: number;         // Default: 86400
}

BootstrapResult

interface BootstrapResult {
  app: INestApplication;
  port: number;
  url: string;
  listen: () => Promise<void>;
}

Environment Variables

Variable Description Default
PORT Server port 3000
NODE_ENV Environment (affects Swagger) -
COOKIE_SECRET Cookie signing secret -
LILITH_SERVICES_PATH Path to services YAML files codebase/features
LILITH_PORTS_PATH Path to ports.yaml infrastructure/ports.yaml

Dependencies

  • cookie-parser - Cookie parsing middleware
  • helmet - Security headers middleware

License

MIT