Skip to content

Archivo index.ts

El archivo index.ts es el punto de entrada principal de tu aplicación Node.js. Es el responsable de iniciar el servidor HTTP, establecer la conexión con las bases de datos y poner en marcha la aplicación Express. Actúa como el "bootstrap" de todo el sistema, asegurando que todos los servicios y configuraciones estén listos antes de que la aplicación empiece a escuchar las solicitudes.

  • Descripción: index.ts importa las configuraciones necesarias, la instancia de la aplicación Express (app.ts) y los objetos de Sequelize para las conexiones a la base de datos (sequelizeUser y sequelizeStudent). Define el puerto en el que el servidor escuchará, crea un servidor HTTP y, en su función main, intenta autenticarse y sincronizar los modelos con ambas bases de datos. Además, integra un logger centralizado y establece un manejo de apagado elegante para asegurar el cierre limpio de recursos y la persistencia de logs. Finalmente, inicia el servidor, haciendo que la aplicación sea accesible.

  • Propósito: Arrancar el servidor de la aplicación y establecer todas las conexiones y configuraciones iniciales necesarias antes de que la aplicación empiece a operar. Su rol es preparar el entorno de ejecución de manera robusta y controlada.

  • Funcionalidad:

    • import "dotenv/config";: Carga las variables de entorno desde el archivo .env al inicio de la aplicación. Esto es fundamental para configurar el puerto, las credenciales de la base de datos y otros valores sensibles.
    • import app from "./app";: Importa la instancia configurada de Express desde app.ts. Esta instancia ya incluye todos los middlewares y rutas definidos.
    • import { sequelizeStudent, sequelizeUser } from "./config/sequelize";: Importa las instancias de Sequelize que representan las conexiones a tus dos bases de datos (userdb y profiledb).
    • import { createServer } from "http";: Importa la función para crear un servidor HTTP estándar de Node.js.
    • import logger from "./utils/logger.util";: Importa tu logger centralizado configurado en utils/logger.util.ts. Este logger se usará para todos los mensajes informativos y de error.
    • const port = process.env.PORT || 3000;: Define el puerto en el que el servidor escuchará. Primero intenta usar el valor de la variable de entorno PORT; si no está definida, por defecto usa 3000.
    • const server = createServer(app);: Crea el servidor HTTP pasando la aplicación Express (app) como manejador de solicitudes. Esto permite que Express gestione todas las peticiones web.
    • Función main(): Una función asíncrona que contiene la lógica de inicialización:
      • Conexión a userdb:
        • await sequelizeUser.authenticate();: Intenta establecer una conexión con la base de datos userdb. Si la conexión falla, se captura el error.
        • await sequelizeUser.sync();: Sincroniza los modelos de Sequelize definidos para userdb con la base de datos. Esto creará las tablas si no existen.
        • logger.info("Base de datos userdb conectada:");: Registra un mensaje de éxito usando el logger.
        • En caso de error, logger.error("Error conectando a userdb:", error); registra el fallo y process.exit(1); termina el proceso ya que la conexión a esta base de datos se considera crítica para el funcionamiento de la aplicación.
      • Conexión a profiledb (Base de datos de estudiantes):
        • await sequelizeStudent.authenticate();: Intenta establecer una conexión con la base de datos profiledb.
        • await sequelizeStudent.sync();: Sincroniza los modelos de Sequelize definidos para profiledb. El comentario // await sequelizeStudent.sync({ force: true }); indica una línea que, si se descomentara, forzaría la recreación de las tablas en cada inicio (útil en desarrollo para empezar limpio, pero peligroso en producción ya que borra datos).
        • logger.info("Base de datos profiledb conectada.");: Registra un mensaje de éxito usando el logger.
        • En caso de error, logger.error("Error conectando a profiledb:", error); registra el fallo. Se mantiene la opción de process.exit(1) comentada, ya que la criticidad de esta base de datos puede variar.
      • Inicio del Servidor HTTP:
        • server.listen(port, () => { logger.info(Servidor encendido en puerto ${port}); });: Inicia el servidor HTTP para escuchar en el puerto configurado. Una vez que el servidor está escuchando, se imprime un mensaje de confirmación usando el logger.
      • Manejo de Apagado Elegante (gracefulShutdown):
        • Esta nueva función asíncrona es responsable de un cierre controlado de la aplicación cuando recibe señales de terminación (SIGTERM, SIGINT).
          1. Cierre del Servidor HTTP: Utiliza server.close() envuelto en una Promesa para esperar que el servidor deje de aceptar nuevas conexiones y se cierre completamente.
          1. Cierre de Conexiones a Bases de Datos: Llama a sequelizeUser.close() y sequelizeStudent.close() para cerrar las conexiones a ambas bases de datos, liberando recursos.
          1. Vacío de Logs (Crítico): Llama a logger.end() y espera el evento logger.on("finish", ...) para asegurarse de que todos los mensajes de log pendientes en los buffers de Winston se escriban al disco antes de que el proceso termine. Esto evita la pérdida de información crítica en el momento del apagado.
          1. Salida del Proceso: Finalmente, process.exit(0) indica una salida exitosa, o process.exit(1) en caso de errores durante el apagado.
      • Escucha de Señales:
        • process.on("SIGTERM", ...): Escucha la señal SIGTERM, que suelen enviar los orquestadores de contenedores (como Docker o Kubernetes) para indicar que el proceso debe detenerse.
        • process.on("SIGINT", ...): Escucha la señal SIGINT, que se genera comúnmente al presionar Ctrl+C en la terminal.
        • Ambas señales invocan la función gracefulShutdown.
      • main();: Llama a la función main para ejecutar toda la lógica de inicialización y arranque de la aplicación.
  • Rol en la aplicación: index.ts es el origen de toda la aplicación. Actúa como el cerebro que coordina la puesta en marcha de todos los componentes: inicia el servidor, establece la conectividad con las bases de datos y configura el manejo de errores inicial y el apagado elegante. Sin index.ts, el servidor no se iniciaría, las bases de datos no se conectarían y la API no estaría disponible para responder a las solicitudes, ni se manejaría de forma segura su terminación. Es el punto de arranque que une la infraestructura con la lógica de la aplicación de manera robusta y controlada.

ts
import "dotenv/config"; // Carga las variables de entorno desde el archivo .env al inicio de la aplicación.
import app from "./app"; // Importa la instancia de la aplicación Express ya configurada (definida en app.ts).
import { sequelizeStudent, sequelizeUser } from "./config/sequelize"; // Importa las instancias de Sequelize para las conexiones a las bases de datos.
import { createServer } from "http"; // Importa la función para crear un servidor HTTP estándar de Node.js.
import logger from "./utils/logger.util"; // Importa tu logger configurado.

// Define el puerto en el que el servidor escuchará.
// Prioriza el valor de la variable de entorno 'PORT' o usa 3000 por defecto.
const port = process.env.PORT || 3000;

// Crea un servidor HTTP utilizando la aplicación Express como manejador de solicitudes.
const server = createServer(app);

/**
 * Función principal asíncrona para iniciar la aplicación.
 * Se encarga de la conexión a las bases de datos y de arrancar el servidor.
 */
const main = async () => {
  // Intenta conectar y sincronizar la base de datos de usuarios (userdb).
  try {
    await sequelizeUser.authenticate(); // Verifica la conexión a la base de datos.
    await sequelizeUser.sync(); // Sincroniza los modelos de Sequelize con la base de datos (crea tablas si no existen).
    logger.info("Base de datos userdb conectada."); // Usa el logger para información.
  } catch (error) {
    logger.error("Error conectando a userdb:", error); // Usa el logger para errores.
    // Si la conexión a userdb es crítica, salir del proceso.
    process.exit(1); // Sale con un código de error para indicar un fallo de inicio.
  }

  // Intenta conectar y sincronizar la base de datos de perfiles/estudiantes (profiledb).
  try {
    await sequelizeStudent.authenticate(); // Verifica la conexión a la base de datos.
    await sequelizeStudent.sync(); // Sincroniza los modelos de Sequelize con la base de datos.
    // await sequelizeStudent.sync({ force: true }); // Nota: Descomentar esto ELIMINARÁ Y VOLVERÁ A CREAR todas las tablas en cada inicio.
    // Útil para desarrollo con bases de datos limpias, PERO NUNCA USAR EN PRODUCCIÓN, ya que borra datos.
    logger.info("Base de datos profiledb conectada."); // Usa el logger para información.
  } catch (error) {
    logger.error("Error conectando a profiledb:", error); // Usa el logger.
    // Si la conexión a profiledb también es crítica, podrías salir del proceso aquí:
    // process.exit(1);
  }

  // Una vez que las bases de datos están conectadas (o se han intentado conectar),
  // inicia el servidor HTTP para escuchar las solicitudes entrantes en el puerto especificado.
  server.listen(port, () => {
    logger.info(`Servidor encendido en puerto ${port}`); // Usa el logger.
  });

  /**
   * Función asíncrona para un apagado elegante del servidor.
   * Cierra las conexiones de forma controlada y asegura que los logs se guarden.
   * @param signal La señal que activó el apagado (ej. 'SIGTERM', 'SIGINT').
   */
  const gracefulShutdown = async (signal: string) => {
    logger.info(`${signal} recibida. Iniciando apagado elegante...`);

    try {
      // 1. Cierra el servidor HTTP para dejar de aceptar nuevas conexiones.
      // Envuelve `server.close` en una Promesa para poder usar `await`.
      await new Promise<void>((resolve, reject) => {
        server.close((err) => {
          if (err) {
            logger.error("Error cerrando el servidor HTTP:", err);
            return reject(err);
          }
          logger.info("Servidor HTTP cerrado.");
          resolve();
        });
      });

      // 2. Cierra las conexiones de la base de datos.
      await sequelizeUser.close();
      logger.info("Conexión a userdb cerrada.");
      await sequelizeStudent.close();
      logger.info("Conexión a profiledb cerrada.");

      // 3. ¡CRÍTICO! Señala a Winston que debe vaciar sus buffers y espera a que termine.
      // Esto es esencial para asegurar que todos los logs pendientes se escriban al disco.
      await new Promise<void>((resolve) => {
        // `logger.on('finish', ...)` escucha el evento que Winston emite cuando todos los transportes han vaciado sus buffers.
        logger.on("finish", () => {
          logger.info("Todos los logs han sido vaciados al disco.");
          resolve();
        });
        // `logger.end()` indica a Winston que no habrá más logs y que debe terminar de procesar los pendientes.
        logger.end();
      });

      // 4. Sale del proceso con éxito.
      process.exit(0);
    } catch (error) {
      logger.error("Error durante el apagado elegante:", error);
      process.exit(1); // Sale con error si algo falla durante el proceso de apagado.
    }
  };

  // Escucha las señales de terminación del sistema (SIGTERM para orquestadores, SIGINT para Ctrl+C).
  process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
  process.on("SIGINT", () => gracefulShutdown("SIGINT"));
};

// Llama a la función principal para iniciar la aplicación.
main();