feat(api): foundation canon JCS — Pino+CLS+RFC9457+health+ping (Frente A)
Estabelece a fundação operacional de apps/api conforme STACK.md v2.2 e
CODING-RULES.md v2.0, substituindo o hello-world scaffolded.
- tracing.ts primeiro import (PGD-OBS-001) — stub OTel ativável por env
- EnvSchema Zod 4 com fail-fast (superRefine guarda prod) + EnvModule global
- nestjs-pino com redact LGPD (*.cpf|*.cardNumber|*.password|auth|cookie)
- main.ts hardenizado: helmet, CORS por env, compression, versionamento URI
/api/v1, graceful shutdown
- ProblemDetailsFilter global (RFC 9457 application/problem+json), Zod -> 422
- Health endpoints /api/v1/health/{live,ready} com memory.checkHeap(350MB)
(ready skeleton documenta K=3 LRU pool conforme PGD-OBS-003)
- WorkspaceModule via ClsModule.forRootAsync — requestId+workspaceId no CLS,
idGenerator alinhado com pino-http para mesmo UUID em header e body
- GET /api/v1/ping retornando workspaceId+requestId (alvo de smoke test e
futuro healthcheck do docker compose)
- ZodValidationPipe (nestjs-zod) como APP_PIPE global
- tsconfig.app.json target ES2023 (alinhado ao base)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,82 @@
|
||||
/**
|
||||
* This is not a production server yet!
|
||||
* This is only a minimal backend to get started.
|
||||
*/
|
||||
// CODING-RULES §09 (PGD-OBS-001): tracing PRECISA ser o primeiro import.
|
||||
// OTel NodeSDK faz monkey-patch dos módulos node:http/pg/etc. antes do Nest.
|
||||
import './tracing';
|
||||
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { VersioningType, type INestApplication } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Logger } from 'nestjs-pino';
|
||||
import helmet from 'helmet';
|
||||
import compression from 'compression';
|
||||
import { AppModule } from './app/app.module';
|
||||
import type { Env } from './app/config/env.schema';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const globalPrefix = 'api';
|
||||
async function bootstrap(): Promise<void> {
|
||||
const app: INestApplication = await NestFactory.create(AppModule, {
|
||||
bufferLogs: true, // logs do boot vão pro Pino quando ele subir
|
||||
});
|
||||
|
||||
// Logger Pino global (substitui o NestLogger padrão).
|
||||
app.useLogger(app.get(Logger));
|
||||
|
||||
const config = app.get(ConfigService) as ConfigService<Env, true>;
|
||||
|
||||
// CORS — origens vindas do EnvSchema (já parseadas em array).
|
||||
app.enableCors({
|
||||
origin: config.get('CORS_ORIGINS', { infer: true }),
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: [
|
||||
'Content-Type',
|
||||
'Authorization',
|
||||
'X-Request-Id',
|
||||
'X-Correlation-Id',
|
||||
'Idempotency-Key',
|
||||
],
|
||||
exposedHeaders: ['X-Request-Id'],
|
||||
maxAge: 600,
|
||||
});
|
||||
|
||||
// Helmet — headers de segurança canônicos.
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: false, // SPA serve CSP via Nginx; API JSON dispensa.
|
||||
crossOriginEmbedderPolicy: false,
|
||||
}),
|
||||
);
|
||||
|
||||
// Compression — gzip/br.
|
||||
app.use(compression());
|
||||
|
||||
// Versionamento via URI: /api/v1/...
|
||||
const globalPrefix = config.get('API_GLOBAL_PREFIX', { infer: true });
|
||||
const apiVersion = config.get('API_VERSION', { infer: true }).replace(/^v/, '');
|
||||
app.setGlobalPrefix(globalPrefix);
|
||||
const port = process.env.PORT || 3000;
|
||||
await app.listen(port);
|
||||
Logger.log(`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`);
|
||||
app.enableVersioning({
|
||||
type: VersioningType.URI,
|
||||
defaultVersion: apiVersion,
|
||||
prefix: 'v',
|
||||
});
|
||||
|
||||
// Graceful shutdown — Nest emite SIGTERM/SIGINT → hooks rodam → dreno de conexões.
|
||||
// Compose: stop_grace_period: 30s + HAProxy drain 30s (CODING-RULES §18).
|
||||
app.enableShutdownHooks();
|
||||
|
||||
const port = config.get('API_PORT', { infer: true });
|
||||
const host = config.get('API_HOST', { infer: true });
|
||||
|
||||
await app.listen(port, host);
|
||||
|
||||
app
|
||||
.get(Logger)
|
||||
.log(
|
||||
`SAR API pronta em http://${host}:${port}/${globalPrefix}/${apiVersion === '0' ? '' : `v${apiVersion}`}`,
|
||||
'Bootstrap',
|
||||
);
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
bootstrap().catch((err) => {
|
||||
// Antes do Logger Pino estar disponível, qualquer erro de boot cai aqui.
|
||||
console.error('[bootstrap] crash fatal — encerrando:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user