# Node.js Experto — Patrones Completos de Referencia

Implementaciones completas de Express, Fastify, streams, worker threads, ORMs y más.

---

## Fastify — Setup Recomendado

```typescript
// src/app.ts
import Fastify from 'fastify';
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';

const app = Fastify({
  logger: {
    level: process.env.LOG_LEVEL ?? 'info',
    transport: process.env.NODE_ENV === 'development'
      ? { target: 'pino-pretty' }
      : undefined,
  },
}).withTypeProvider<TypeBoxTypeProvider>();

await app.register(import('@fastify/cors'), {
  origin: process.env.CORS_ORIGIN?.split(',') ?? false,
});
await app.register(import('@fastify/helmet'));
await app.register(import('./routes/usuarios'), { prefix: '/api/v1/usuarios' });

export { app };
```

## Express — Con TypeScript Moderno

```typescript
import express, { Express } from 'express';
import { usuariosRouter } from './routes/usuarios';
import { errorMiddleware } from './middleware/error';

export function createApp(): Express {
  const app = express();
  app.use(express.json());
  app.use(express.urlencoded({ extended: true }));
  app.use('/api/v1/usuarios', usuariosRouter);
  app.use(errorMiddleware);
  return app;
}
```

---

## Streams y Buffers — Ejemplos Completos

### Procesar archivo grande sin cargar en memoria

```typescript
import { createReadStream, createWriteStream } from 'fs';
import { pipeline } from 'stream/promises';
import { createGzip } from 'zlib';
import { Transform } from 'stream';

async function procesarArchivoGrande(entrada: string, salida: string): Promise<void> {
  const transformador = new Transform({
    objectMode: false,
    transform(chunk: Buffer, _encoding, callback) {
      const procesado = chunk.toString().toUpperCase();
      callback(null, procesado);
    },
  });
  await pipeline(createReadStream(entrada), transformador, createGzip(), createWriteStream(salida));
}
```

### Streaming HTTP response

```typescript
import { Readable } from 'stream';

app.get('/exportar', async (req, reply) => {
  reply.header('Content-Type', 'text/csv');
  reply.header('Content-Disposition', 'attachment; filename="export.csv"');
  const stream = Readable.from(generarCSV());
  return reply.send(stream);
});

async function* generarCSV(): AsyncGenerator<string> {
  yield 'id,nombre,email\n';
  const cursor = db.usuario.cursor();
  for await (const usuario of cursor) {
    yield `${usuario.id},${usuario.nombre},${usuario.email}\n`;
  }
}
```

---

## Worker Threads y Clustering

### Worker Threads

```typescript
// src/workers/procesador.worker.ts
import { workerData, parentPort } from 'worker_threads';

function procesarIntensivo(datos: number[]): number {
  return datos.reduce((acc, n) => acc + Math.sqrt(n), 0);
}

const resultado = procesarIntensivo(workerData.datos);
parentPort!.postMessage({ resultado });

// src/services/procesamiento.service.ts
import { Worker } from 'worker_threads';
import path from 'path';

export function procesarEnWorker(datos: number[]): Promise<number> {
  return new Promise((resolve, reject) => {
    const worker = new Worker(
      path.resolve(__dirname, '../workers/procesador.worker.js'),
      { workerData: { datos } },
    );
    worker.once('message', ({ resultado }) => resolve(resultado));
    worker.once('error', reject);
    worker.once('exit', code => {
      if (code !== 0) reject(new Error(`Worker salió con código ${code}`));
    });
  });
}
```

### Pool de workers con Piscina

```typescript
import { Piscina } from 'piscina';

const pool = new Piscina({
  filename: path.resolve(__dirname, 'workers/procesador.worker.js'),
  maxThreads: Math.max(1, os.cpus().length - 1),
});

const resultado = await pool.run({ datos: arrayGrande });
```

### Clustering

```typescript
import cluster from 'cluster';
import os from 'os';

if (cluster.isPrimary) {
  const numCPUs = os.cpus().length;
  for (let i = 0; i < numCPUs; i++) cluster.fork();
  cluster.on('exit', (worker, code) => {
    console.log(`Worker ${worker.process.pid} murió (código ${code}), reiniciando...`);
    cluster.fork();
  });
} else {
  const { app } = await import('./app');
  app.listen(3000, () => console.log(`Worker ${process.pid} escuchando en 3000`));
}
```

---

## Prisma — Patterns de Producción

```typescript
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    log: process.env.NODE_ENV === 'development' ? ['query', 'warn', 'error'] : ['error'],
  });

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

// Query tipada con include
const usuario = await prisma.usuario.findUniqueOrThrow({
  where: { id },
  include: { perfil: true, roles: { include: { permisos: true } } },
});

// Transacción
const resultado = await prisma.$transaction(async (tx) => {
  const user = await tx.usuario.create({ data: usuarioData });
  await tx.auditLog.create({ data: { accion: 'CREATE', entityId: user.id } });
  return user;
});
```

## Drizzle — Schema y Queries

```typescript
import { pgTable, uuid, varchar, boolean, timestamp } from 'drizzle-orm/pg-core';

export const usuarios = pgTable('usuarios', {
  id: uuid('id').defaultRandom().primaryKey(),
  nombre: varchar('nombre', { length: 255 }).notNull(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  activo: boolean('activo').default(true).notNull(),
  creadoEn: timestamp('creado_en').defaultNow().notNull(),
});

const activos = await db
  .select().from(usuarios)
  .where(eq(usuarios.activo, true))
  .orderBy(asc(usuarios.nombre))
  .limit(20);
```

---

## Validación con Zod

```typescript
import { z } from 'zod';

export const UsuarioCreateSchema = z.object({
  nombre: z.string().min(2, 'Nombre muy corto').max(100),
  email: z.string().email('Email inválido'),
  edad: z.number().int().min(18).max(120).optional(),
  rol: z.enum(['admin', 'usuario', 'moderador']).default('usuario'),
});

export type UsuarioCreate = z.infer<typeof UsuarioCreateSchema>;

export function validar<T>(schema: z.ZodSchema<T>) {
  return asyncHandler(async (req, _res, next) => {
    const resultado = schema.safeParse(req.body);
    if (!resultado.success) {
      throw new ValidationError(
        'Datos de entrada inválidos',
        resultado.error.flatten().fieldErrors as Record<string, string[]>,
      );
    }
    req.body = resultado.data;
    next();
  });
}
```

---

## Graceful Shutdown

```typescript
import { app } from './app';
import { prisma } from './lib/prisma';
import { logger } from './lib/logger';

const server = app.listen(3000);

async function shutdown(signal: string): Promise<void> {
  logger.info(`Recibida señal ${signal}, iniciando graceful shutdown`);
  server.close(async () => {
    try {
      await prisma.$disconnect();
      logger.info('Graceful shutdown completado');
      process.exit(0);
    } catch (err) {
      logger.error({ err }, 'Error durante graceful shutdown');
      process.exit(1);
    }
  });
  setTimeout(() => {
    logger.error('Graceful shutdown timeout — forzando cierre');
    process.exit(1);
  }, 10_000);
}

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('unhandledRejection', (reason, promise) => {
  logger.error({ reason, promise }, 'Unhandled Promise Rejection');
  shutdown('unhandledRejection');
});
process.on('uncaughtException', (err) => {
  logger.error({ err }, 'Uncaught Exception');
  process.exit(1);
});
```
