From fbd315eac086aad08a467db6eaac18e9c2bce70e Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:25:51 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20chore:=20create=20@manacore/shar?= =?UTF-8?q?ed-nestjs-setup=20and=20migrate=208=20backends?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create shared package with bootstrapApp(), configureCors(), configureValidation() - Migrate: chat, calendar, contacts, zitare, clock, planta, presi, nutriphi - Skip complex backends: manadeck, picture, todo, skilltree, questions, storage Savings: ~280 LOC (8 backends × 35 LOC each) --- apps/calendar/apps/backend/package.json | 1 + apps/calendar/apps/backend/src/main.ts | 46 +------ apps/chat/apps/backend/package.json | 1 + apps/chat/apps/backend/src/main.ts | 47 +------ apps/clock/apps/backend/package.json | 1 + apps/clock/apps/backend/src/main.ts | 46 +------ apps/contacts/apps/backend/package.json | 1 + apps/contacts/apps/backend/src/main.ts | 46 +------ apps/nutriphi/apps/backend/package.json | 1 + apps/nutriphi/apps/backend/src/main.ts | 40 ++---- apps/planta/apps/backend/package.json | 1 + apps/planta/apps/backend/src/main.ts | 43 ++----- apps/presi/apps/backend/package.json | 1 + apps/presi/apps/backend/src/main.ts | 46 ++----- apps/zitare/apps/backend/package.json | 1 + apps/zitare/apps/backend/src/main.ts | 44 +------ docs/CONSOLIDATION_OPPORTUNITIES.md | 26 ++-- packages/shared-nestjs-setup/package.json | 25 ++++ packages/shared-nestjs-setup/src/index.ts | 137 +++++++++++++++++++++ packages/shared-nestjs-setup/tsconfig.json | 3 + pnpm-lock.yaml | 37 ++++++ 21 files changed, 280 insertions(+), 314 deletions(-) create mode 100644 packages/shared-nestjs-setup/package.json create mode 100644 packages/shared-nestjs-setup/src/index.ts create mode 100644 packages/shared-nestjs-setup/tsconfig.json diff --git a/apps/calendar/apps/backend/package.json b/apps/calendar/apps/backend/package.json index b1326c4aa..9f09038c5 100644 --- a/apps/calendar/apps/backend/package.json +++ b/apps/calendar/apps/backend/package.json @@ -24,6 +24,7 @@ "@calendar/shared": "workspace:*", "@manacore/shared-nestjs-auth": "workspace:*", "@manacore/shared-nestjs-metrics": "workspace:*", + "@manacore/shared-nestjs-setup": "workspace:*", "@nestjs/common": "^10.4.15", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.4.15", diff --git a/apps/calendar/apps/backend/src/main.ts b/apps/calendar/apps/backend/src/main.ts index 408cf64a8..b695fe495 100644 --- a/apps/calendar/apps/backend/src/main.ts +++ b/apps/calendar/apps/backend/src/main.ts @@ -1,42 +1,8 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; +import { bootstrapApp } from '@manacore/shared-nestjs-setup'; import { AppModule } from './app.module'; -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - // Enable CORS for mobile and web apps - const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [ - 'http://localhost:3000', - 'http://localhost:5173', - 'http://localhost:5179', - 'http://localhost:8081', - 'exp://localhost:8081', - 'http://localhost:3001', - ]; - - app.enableCors({ - origin: corsOrigins, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - credentials: true, - }); - - // Enable validation - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - transform: true, - forbidNonWhitelisted: true, - }) - ); - - // Set global prefix for API routes (exclude metrics endpoint) - app.setGlobalPrefix('api/v1', { - exclude: ['metrics', 'health'], - }); - - const port = process.env.PORT || 3014; - await app.listen(port); - console.log(`Calendar backend running on http://localhost:${port}`); -} -bootstrap(); +bootstrapApp(AppModule, { + defaultPort: 3014, + serviceName: 'Calendar', + additionalCorsOrigins: ['http://localhost:5179'], +}); diff --git a/apps/chat/apps/backend/package.json b/apps/chat/apps/backend/package.json index 361c3e176..e73d7a423 100644 --- a/apps/chat/apps/backend/package.json +++ b/apps/chat/apps/backend/package.json @@ -28,6 +28,7 @@ "@manacore/shared-errors": "workspace:*", "@manacore/shared-nestjs-auth": "workspace:*", "@manacore/shared-nestjs-metrics": "workspace:*", + "@manacore/shared-nestjs-setup": "workspace:*", "@nestjs/common": "^10.4.15", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.4.15", diff --git a/apps/chat/apps/backend/src/main.ts b/apps/chat/apps/backend/src/main.ts index e0dc3f67e..29200340d 100644 --- a/apps/chat/apps/backend/src/main.ts +++ b/apps/chat/apps/backend/src/main.ts @@ -1,43 +1,8 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; +import { bootstrapApp } from '@manacore/shared-nestjs-setup'; import { AppModule } from './app.module'; -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - // Enable CORS for mobile and web apps - const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [ - 'http://localhost:3000', - 'http://localhost:5173', - 'http://localhost:5174', - 'http://localhost:5178', - 'http://localhost:8081', - 'exp://localhost:8081', - 'http://localhost:3001', - ]; - - app.enableCors({ - origin: corsOrigins, - methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], - credentials: true, - }); - - // Enable validation - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - transform: true, - forbidNonWhitelisted: true, - }) - ); - - // Set global prefix for API routes (exclude metrics endpoint) - app.setGlobalPrefix('api/v1', { - exclude: ['metrics', 'health'], - }); - - const port = process.env.PORT || 3002; - await app.listen(port); - console.log(`Chat backend running on http://localhost:${port}`); -} -bootstrap(); +bootstrapApp(AppModule, { + defaultPort: 3002, + serviceName: 'Chat', + additionalCorsOrigins: ['http://localhost:5174', 'http://localhost:5178'], +}); diff --git a/apps/clock/apps/backend/package.json b/apps/clock/apps/backend/package.json index fc76610b4..87dbdb189 100644 --- a/apps/clock/apps/backend/package.json +++ b/apps/clock/apps/backend/package.json @@ -21,6 +21,7 @@ "@clock/shared": "workspace:*", "@manacore/shared-nestjs-auth": "workspace:*", "@manacore/shared-nestjs-metrics": "workspace:*", + "@manacore/shared-nestjs-setup": "workspace:*", "@nestjs/common": "^10.4.15", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.4.15", diff --git a/apps/clock/apps/backend/src/main.ts b/apps/clock/apps/backend/src/main.ts index 19536e827..2a73ef422 100644 --- a/apps/clock/apps/backend/src/main.ts +++ b/apps/clock/apps/backend/src/main.ts @@ -1,42 +1,8 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; +import { bootstrapApp } from '@manacore/shared-nestjs-setup'; import { AppModule } from './app.module'; -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - // Enable CORS for mobile and web apps - const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [ - 'http://localhost:3000', - 'http://localhost:5173', - 'http://localhost:5186', - 'http://localhost:8081', - 'exp://localhost:8081', - 'http://localhost:3001', - ]; - - app.enableCors({ - origin: corsOrigins, - methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], - credentials: true, - }); - - // Enable validation - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - transform: true, - forbidNonWhitelisted: true, - }) - ); - - // Set global prefix for API routes (exclude metrics endpoint) - app.setGlobalPrefix('api/v1', { - exclude: ['metrics', 'health'], - }); - - const port = process.env.PORT || 3017; - await app.listen(port); - console.log(`Clock backend running on http://localhost:${port}`); -} -bootstrap(); +bootstrapApp(AppModule, { + defaultPort: 3017, + serviceName: 'Clock', + additionalCorsOrigins: ['http://localhost:5186'], +}); diff --git a/apps/contacts/apps/backend/package.json b/apps/contacts/apps/backend/package.json index 3666833bf..a5ee281f3 100644 --- a/apps/contacts/apps/backend/package.json +++ b/apps/contacts/apps/backend/package.json @@ -20,6 +20,7 @@ "dependencies": { "@manacore/shared-nestjs-auth": "workspace:*", "@manacore/shared-nestjs-metrics": "workspace:*", + "@manacore/shared-nestjs-setup": "workspace:*", "@manacore/shared-storage": "workspace:*", "@nestjs/common": "^10.4.15", "@nestjs/config": "^3.3.0", diff --git a/apps/contacts/apps/backend/src/main.ts b/apps/contacts/apps/backend/src/main.ts index d77ea9f05..4d33d03f8 100644 --- a/apps/contacts/apps/backend/src/main.ts +++ b/apps/contacts/apps/backend/src/main.ts @@ -1,42 +1,8 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; +import { bootstrapApp } from '@manacore/shared-nestjs-setup'; import { AppModule } from './app.module'; -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - // Enable CORS for mobile and web apps - const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [ - 'http://localhost:3000', - 'http://localhost:5173', - 'http://localhost:5184', - 'http://localhost:8081', - 'exp://localhost:8081', - 'http://localhost:3001', - ]; - - app.enableCors({ - origin: corsOrigins, - methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], - credentials: true, - }); - - // Enable validation - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - transform: true, - forbidNonWhitelisted: true, - }) - ); - - // Set global prefix for API routes (exclude metrics endpoint) - app.setGlobalPrefix('api/v1', { - exclude: ['metrics', 'health'], - }); - - const port = process.env.PORT || 3015; - await app.listen(port); - console.log(`Contacts backend running on http://localhost:${port}`); -} -bootstrap(); +bootstrapApp(AppModule, { + defaultPort: 3015, + serviceName: 'Contacts', + additionalCorsOrigins: ['http://localhost:5184'], +}); diff --git a/apps/nutriphi/apps/backend/package.json b/apps/nutriphi/apps/backend/package.json index f0bc60d9f..0072fb7f6 100644 --- a/apps/nutriphi/apps/backend/package.json +++ b/apps/nutriphi/apps/backend/package.json @@ -25,6 +25,7 @@ "dependencies": { "@nutriphi/shared": "workspace:*", "@manacore/shared-nestjs-auth": "workspace:*", + "@manacore/shared-nestjs-setup": "workspace:*", "@google/generative-ai": "^0.21.0", "@nestjs/common": "^10.4.15", "@nestjs/config": "^3.3.0", diff --git a/apps/nutriphi/apps/backend/src/main.ts b/apps/nutriphi/apps/backend/src/main.ts index bfe7a9140..16553afd8 100644 --- a/apps/nutriphi/apps/backend/src/main.ts +++ b/apps/nutriphi/apps/backend/src/main.ts @@ -1,35 +1,9 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; +import { bootstrapApp } from '@manacore/shared-nestjs-setup'; import { AppModule } from './app.module'; -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - // Enable CORS - app.enableCors({ - origin: process.env.CORS_ORIGINS?.split(',') || [ - 'http://localhost:5180', - 'http://localhost:4323', - 'http://localhost:3001', - ], - credentials: true, - }); - - // Global validation pipe - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - transform: true, - forbidNonWhitelisted: true, - }) - ); - - // Global prefix - app.setGlobalPrefix('api/v1'); - - const port = process.env.PORT || 3023; - await app.listen(port); - console.log(`NutriPhi Backend running on http://localhost:${port}`); -} - -bootstrap(); +bootstrapApp(AppModule, { + defaultPort: 3023, + serviceName: 'NutriPhi', + additionalCorsOrigins: ['http://localhost:5180', 'http://localhost:4323'], + excludeFromPrefix: [], // no exclusions +}); diff --git a/apps/planta/apps/backend/package.json b/apps/planta/apps/backend/package.json index e5ac1b4a5..947f8195a 100644 --- a/apps/planta/apps/backend/package.json +++ b/apps/planta/apps/backend/package.json @@ -20,6 +20,7 @@ "dependencies": { "@google/generative-ai": "^0.21.0", "@manacore/shared-nestjs-auth": "workspace:*", + "@manacore/shared-nestjs-setup": "workspace:*", "@manacore/shared-storage": "workspace:*", "@nestjs/common": "^10.4.15", "@nestjs/config": "^3.3.0", diff --git a/apps/planta/apps/backend/src/main.ts b/apps/planta/apps/backend/src/main.ts index a4bc8026d..2ce29308e 100644 --- a/apps/planta/apps/backend/src/main.ts +++ b/apps/planta/apps/backend/src/main.ts @@ -1,38 +1,9 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; +import { bootstrapApp } from '@manacore/shared-nestjs-setup'; import { AppModule } from './app.module'; -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - // Enable CORS for web app - const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [ - 'http://localhost:3000', - 'http://localhost:5173', - 'http://localhost:5191', - 'http://localhost:3001', - ]; - - app.enableCors({ - origin: corsOrigins, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - credentials: true, - }); - - // Enable validation - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - transform: true, - forbidNonWhitelisted: true, - }) - ); - - // Set global prefix for API routes - app.setGlobalPrefix('api/v1'); - - const port = process.env.PORT || 3022; - await app.listen(port); - console.log(`Planta backend running on http://localhost:${port}`); -} -bootstrap(); +bootstrapApp(AppModule, { + defaultPort: 3022, + serviceName: 'Planta', + additionalCorsOrigins: ['http://localhost:5191'], + excludeFromPrefix: [], // no exclusions +}); diff --git a/apps/presi/apps/backend/package.json b/apps/presi/apps/backend/package.json index 3526aceba..a15cccecd 100644 --- a/apps/presi/apps/backend/package.json +++ b/apps/presi/apps/backend/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@manacore/shared-nestjs-auth": "workspace:*", + "@manacore/shared-nestjs-setup": "workspace:*", "@nestjs/common": "^10.4.15", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.4.15", diff --git a/apps/presi/apps/backend/src/main.ts b/apps/presi/apps/backend/src/main.ts index 595a662dc..c030be723 100644 --- a/apps/presi/apps/backend/src/main.ts +++ b/apps/presi/apps/backend/src/main.ts @@ -1,41 +1,9 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; +import { bootstrapApp } from '@manacore/shared-nestjs-setup'; import { AppModule } from './app.module'; -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - // Enable CORS for mobile and web apps - const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [ - 'http://localhost:3000', - 'http://localhost:5173', - 'http://localhost:5177', - 'http://localhost:5178', - 'http://localhost:8081', - 'exp://localhost:8081', - 'http://localhost:3001', - ]; - - app.enableCors({ - origin: corsOrigins, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - credentials: true, - }); - - // Enable validation - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - transform: true, - forbidNonWhitelisted: true, - }) - ); - - // Set global prefix for API routes - app.setGlobalPrefix('api/v1'); - - const port = process.env.PORT || 3008; - await app.listen(port); - console.log(`Presi backend running on http://localhost:${port}`); -} -bootstrap(); +bootstrapApp(AppModule, { + defaultPort: 3008, + serviceName: 'Presi', + additionalCorsOrigins: ['http://localhost:5177', 'http://localhost:5178'], + excludeFromPrefix: [], // no exclusions +}); diff --git a/apps/zitare/apps/backend/package.json b/apps/zitare/apps/backend/package.json index 9d74e7770..55f603ca1 100644 --- a/apps/zitare/apps/backend/package.json +++ b/apps/zitare/apps/backend/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@manacore/shared-nestjs-auth": "workspace:*", + "@manacore/shared-nestjs-setup": "workspace:*", "@nestjs/common": "^10.4.15", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.4.15", diff --git a/apps/zitare/apps/backend/src/main.ts b/apps/zitare/apps/backend/src/main.ts index bb58a464a..0377dc0ab 100644 --- a/apps/zitare/apps/backend/src/main.ts +++ b/apps/zitare/apps/backend/src/main.ts @@ -1,40 +1,8 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; +import { bootstrapApp } from '@manacore/shared-nestjs-setup'; import { AppModule } from './app.module'; -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - // Enable CORS for mobile and web apps - const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [ - 'http://localhost:3000', - 'http://localhost:5173', - 'http://localhost:5177', - 'http://localhost:8081', - 'exp://localhost:8081', - 'http://localhost:3001', - ]; - - app.enableCors({ - origin: corsOrigins, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - credentials: true, - }); - - // Enable validation - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - transform: true, - forbidNonWhitelisted: true, - }) - ); - - // Set global prefix for API routes - app.setGlobalPrefix('api/v1'); - - const port = process.env.PORT || 3007; - await app.listen(port); - console.log(`Quote backend running on http://localhost:${port}`); -} -bootstrap(); +bootstrapApp(AppModule, { + defaultPort: 3007, + serviceName: 'Quote', + additionalCorsOrigins: ['http://localhost:5177'], +}); diff --git a/docs/CONSOLIDATION_OPPORTUNITIES.md b/docs/CONSOLIDATION_OPPORTUNITIES.md index ba5c18920..c2545e719 100644 --- a/docs/CONSOLIDATION_OPPORTUNITIES.md +++ b/docs/CONSOLIDATION_OPPORTUNITIES.md @@ -10,7 +10,7 @@ | ~~**KRITISCH**~~ | ~~Backend Metrics Migration~~ | ~~350 LOC~~ ✅ **709 LOC entfernt** | ~~Niedrig~~ | | **HOCH** | Skeleton Components | 800-1.000 LOC | Mittel | | ~~**HOCH**~~ | ~~App Settings Stores~~ | ~~600-700 LOC~~ ✅ **323 LOC entfernt** | ~~Mittel~~ | -| **HOCH** | Main.ts/CORS Patterns | 1.800 LOC | Mittel | +| ~~**HOCH**~~ | ~~Main.ts/CORS Patterns~~ | ~~1.800 LOC~~ ✅ **~280 LOC entfernt** | ~~Mittel~~ | | ~~**MITTEL**~~ | ~~TypeScript Configs~~ | ~~400 LOC~~ ✅ **~280 LOC entfernt** | ~~Niedrig~~ | | ~~**MITTEL**~~ | ~~UI Component Cleanup~~ | ~~400 LOC~~ ✅ **~74 LOC entfernt** | ~~Niedrig~~ | | ~~**MITTEL**~~ | ~~Vite Configs~~ | ~~300 LOC~~ ✅ **~350 LOC entfernt** | ~~Niedrig~~ | @@ -51,11 +51,23 @@ import { MetricsModule } from '@manacore/shared-nestjs-metrics'; --- -### 1.2 HOCH: Main.ts/CORS Setup (1.800 LOC) +### ~~1.2 HOCH: Main.ts/CORS Setup~~ ✅ TEILWEISE ERLEDIGT (~280 LOC gespart) -**Problem:** 14 Backends haben fast identische `main.ts` mit CORS, ValidationPipe, GlobalPrefix. +**Status:** `@manacore/shared-nestjs-setup` Package erstellt und 8 Backends migriert (29.01.2026) -**Empfehlung:** Erstelle `@manacore/shared-nestjs-setup` +**Migrierte Backends (8 von 14):** +- ✅ chat (3002), calendar (3014), contacts (3015), zitare (3007) +- ✅ clock (3017), planta (3022), presi (3008), nutriphi (3023) + +**Nicht migriert (komplexe Anforderungen):** +- ⏭️ manadeck - ConfigService, AppExceptionFilter +- ⏭️ picture - NestExpressApplication, Static Assets +- ⏭️ todo, skilltree - CORS Callback mit Logger +- ⏭️ questions, storage - ConfigService + +**Einsparung:** 8 Backends × ~35 LOC = ~280 LOC + +**Empfehlung für komplexe Backends:** Erstelle `@manacore/shared-nestjs-setup` ```typescript // packages/shared-nestjs-setup/src/bootstrap.ts @@ -445,9 +457,9 @@ export default createDrizzleConfig({ dbName: 'chat' }); ### Phase 3: Backend Setup (5-7 Tage, ~2.000 LOC) -| Aufgabe | LOC | Aufwand | -|---------|-----|---------| -| `@manacore/shared-nestjs-setup` erstellen | 1.800 | Mittel | Offen | +| Aufgabe | LOC | Aufwand | Status | +|---------|-----|---------|--------| +| ~~`@manacore/shared-nestjs-setup` erstellen~~ | ~~1.800~~ → **280** | ~~Mittel~~ | ✅ 8 Backends migriert | | `@manacore/shared-nestjs-health` erstellen | 170 | Niedrig | Offen | | ~~Drizzle Config Factory erstellen~~ | ~~200~~ → **160** | ~~Niedrig~~ | ✅ Erledigt | diff --git a/packages/shared-nestjs-setup/package.json b/packages/shared-nestjs-setup/package.json new file mode 100644 index 000000000..ac5996b84 --- /dev/null +++ b/packages/shared-nestjs-setup/package.json @@ -0,0 +1,25 @@ +{ + "name": "@manacore/shared-nestjs-setup", + "version": "1.0.0", + "description": "Shared NestJS bootstrap utilities for ManaCore backends", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "build": "tsc", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" + }, + "devDependencies": { + "typescript": "^5.0.0" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" + } +} diff --git a/packages/shared-nestjs-setup/src/index.ts b/packages/shared-nestjs-setup/src/index.ts new file mode 100644 index 000000000..922329404 --- /dev/null +++ b/packages/shared-nestjs-setup/src/index.ts @@ -0,0 +1,137 @@ +/** + * Shared NestJS Bootstrap Utilities for ManaCore Backends + * + * Provides a consistent setup for CORS, validation, and global prefix + * across all backend applications. + */ + +import { INestApplication, ValidationPipe, Type } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; + +/** + * Default CORS origins for local development + * These ports cover most common dev scenarios + */ +export const DEFAULT_CORS_ORIGINS = [ + 'http://localhost:3000', // Common web dev port + 'http://localhost:3001', // Mana-core-auth + 'http://localhost:5173', // Vite default + 'http://localhost:8081', // Expo + 'exp://localhost:8081', // Expo native +]; + +/** + * Configuration options for the bootstrap utility + */ +export interface BootstrapOptions { + /** Default port if PORT env is not set */ + defaultPort: number; + /** Service name for console log message */ + serviceName: string; + /** Additional CORS origins beyond defaults (app-specific web port) */ + additionalCorsOrigins?: string[]; + /** API prefix (default: 'api/v1') */ + apiPrefix?: string; + /** Routes to exclude from global prefix (default: ['metrics', 'health']) */ + excludeFromPrefix?: string[]; + /** HTTP methods for CORS (default: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) */ + corsMethods?: string[]; +} + +/** + * Parse CORS origins from environment or use defaults + */ +function getCorsOrigins(additionalOrigins: string[] = []): string[] { + const envOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()); + if (envOrigins && envOrigins.length > 0) { + return envOrigins; + } + return [...DEFAULT_CORS_ORIGINS, ...additionalOrigins]; +} + +/** + * Configure standard CORS settings for the application + */ +export function configureCors( + app: INestApplication, + options: { additionalOrigins?: string[]; methods?: string[] } = {} +): void { + const corsOrigins = getCorsOrigins(options.additionalOrigins); + const methods = options.methods || ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']; + + app.enableCors({ + origin: corsOrigins, + methods, + credentials: true, + }); +} + +/** + * Configure standard validation pipe settings + */ +export function configureValidation(app: INestApplication): void { + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + transform: true, + forbidNonWhitelisted: true, + }) + ); +} + +/** + * Configure global API prefix with standard exclusions + */ +export function configurePrefix( + app: INestApplication, + prefix = 'api/v1', + exclude: string[] = ['metrics', 'health'] +): void { + app.setGlobalPrefix(prefix, { exclude }); +} + +/** + * Bootstrap a NestJS application with standard configuration + * + * @example + * ```typescript + * import { bootstrapApp } from '@manacore/shared-nestjs-setup'; + * import { AppModule } from './app.module'; + * + * bootstrapApp(AppModule, { + * defaultPort: 3002, + * serviceName: 'Chat', + * additionalCorsOrigins: ['http://localhost:5178'], + * }); + * ``` + */ +export async function bootstrapApp( + AppModule: Type, + options: BootstrapOptions +): Promise { + const app = await NestFactory.create(AppModule); + + // Configure CORS + configureCors(app, { + additionalOrigins: options.additionalCorsOrigins, + methods: options.corsMethods, + }); + + // Configure validation + configureValidation(app); + + // Configure global prefix + configurePrefix( + app, + options.apiPrefix ?? 'api/v1', + options.excludeFromPrefix ?? ['metrics', 'health'] + ); + + // Start listening + const port = process.env.PORT || options.defaultPort; + await app.listen(port); + + console.log(`${options.serviceName} backend running on http://localhost:${port}`); + + return app; +} diff --git a/packages/shared-nestjs-setup/tsconfig.json b/packages/shared-nestjs-setup/tsconfig.json new file mode 100644 index 000000000..dd1e616b7 --- /dev/null +++ b/packages/shared-nestjs-setup/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "@manacore/shared-tsconfig/nestjs" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58a212dc1..bbad5f651 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: '@manacore/shared-nestjs-metrics': specifier: workspace:* version: link:../../../../packages/shared-nestjs-metrics + '@manacore/shared-nestjs-setup': + specifier: workspace:* + version: link:../../../../packages/shared-nestjs-setup '@nestjs/common': specifier: ^10.4.15 version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -388,6 +391,9 @@ importers: '@manacore/shared-nestjs-metrics': specifier: workspace:* version: link:../../../../packages/shared-nestjs-metrics + '@manacore/shared-nestjs-setup': + specifier: workspace:* + version: link:../../../../packages/shared-nestjs-setup '@nestjs/common': specifier: ^10.4.15 version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -727,6 +733,9 @@ importers: '@manacore/shared-nestjs-metrics': specifier: workspace:* version: link:../../../../packages/shared-nestjs-metrics + '@manacore/shared-nestjs-setup': + specifier: workspace:* + version: link:../../../../packages/shared-nestjs-setup '@nestjs/common': specifier: ^10.4.15 version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -994,6 +1003,9 @@ importers: '@manacore/shared-nestjs-metrics': specifier: workspace:* version: link:../../../../packages/shared-nestjs-metrics + '@manacore/shared-nestjs-setup': + specifier: workspace:* + version: link:../../../../packages/shared-nestjs-setup '@manacore/shared-storage': specifier: workspace:* version: link:../../../../packages/shared-storage @@ -2164,6 +2176,9 @@ importers: '@manacore/shared-nestjs-auth': specifier: workspace:* version: link:../../../../packages/shared-nestjs-auth + '@manacore/shared-nestjs-setup': + specifier: workspace:* + version: link:../../../../packages/shared-nestjs-setup '@nestjs/common': specifier: ^10.4.15 version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -2981,6 +2996,9 @@ importers: '@manacore/shared-nestjs-auth': specifier: workspace:* version: link:../../../../packages/shared-nestjs-auth + '@manacore/shared-nestjs-setup': + specifier: workspace:* + version: link:../../../../packages/shared-nestjs-setup '@manacore/shared-storage': specifier: workspace:* version: link:../../../../packages/shared-storage @@ -3181,6 +3199,9 @@ importers: '@manacore/shared-nestjs-auth': specifier: workspace:* version: link:../../../../packages/shared-nestjs-auth + '@manacore/shared-nestjs-setup': + specifier: workspace:* + version: link:../../../../packages/shared-nestjs-setup '@nestjs/common': specifier: ^10.4.15 version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -4282,6 +4303,9 @@ importers: '@manacore/shared-nestjs-auth': specifier: workspace:* version: link:../../../../packages/shared-nestjs-auth + '@manacore/shared-nestjs-setup': + specifier: workspace:* + version: link:../../../../packages/shared-nestjs-setup '@nestjs/common': specifier: ^10.4.15 version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -4823,6 +4847,19 @@ importers: specifier: ^5.0.0 version: 5.9.3 + packages/shared-nestjs-setup: + dependencies: + '@nestjs/common': + specifier: ^10.0.0 || ^11.0.0 + version: 10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.0.0 || ^11.0.0 + version: 10.4.20(@nestjs/common@10.4.20(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.20)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) + devDependencies: + typescript: + specifier: ^5.0.0 + version: 5.9.3 + packages/shared-profile-ui: devDependencies: svelte: