|
Some checks failed
Build and Publish (Auto-detect) / build-and-publish (push) Failing after 42s
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| src | ||
| .gitignore | ||
| eslint.config.js | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsup.config.ts | ||
@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- IncludeCORS_ORIGINSenv 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 serversfrontend-admin- Admin interfacesfrontend-public- Public-facing interfacesweb- 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:
-
Multi-strategy detection - Dependencies are checked via:
- PID file (did we start it?)
- HTTP health endpoint
- Docker container status
- TCP port availability
-
Idempotent behavior:
- Already running → Detected and accepted (continues immediately)
- Not running → Started with health checks
- Crashed (stale PID) → Detected and restarted
-
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 middlewarehelmet- Security headers middleware
License
MIT