Lista de verificación de revisión de código Node.js: todos los pasos incluidos

Node.js ha pasado de ser un experimento audaz a la columna vertebral del desarrollo backend moderno. A partir de 2025, el 48,7% de los desarrolladores de todo el mundo utilizan Node.js, lo que lo convierte en el framework web más adoptado del planeta, según la Encuesta de Desarrolladores de Stack Overflow. Empresas como Netflix, PayPal y Amazon ejecutan infraestructuras críticas sobre él. El ecosistema npm ahora alberga más de 2 millones de paquetes. Sin embargo, ninguna de esas popularidades protege una base de código de los problemas que surgen cuando nadie presta suficiente atención.

Lamentablemente, un tiempo de ejecución rápido no hace que una aplicación sea segura o mantenible. Solo hace que los problemas escalen más rápido. Es por eso que una revisión de código estructurada es una de las inversiones más importantes que un equipo de Node.js puede hacer, ya sea que se esté preparando para una ronda de financiación, incorporando un nuevo equipo de ingeniería o lanzando un producto que planea mantener durante los próximos cinco años.

En Redwerk, hemos estado creando y auditando Node.js durante años, y esta lista de verificación refleja lo que nuestros ingenieros buscan durante cada revisión. La organizamos de la misma manera en que ejecutamos el proceso nosotros mismos.

Preparación previa a la revisión

En primer lugar, iniciar una revisión sin preparación es como entrar en una negociación sin haber leído el resumen. Significa que aún puedes llegar a alguna parte, pero te perderás las cosas que más importan.

Definir el alcance y los objetivos:

  • Primero, identifica si la revisión cubre la base de código completa o si te centrarás en un módulo específico, una capa de API o una versión reciente
  • Establece criterios de éxito claros. Significa definir qué cuenta como una revisión aprobada. ¿Es tener menos problemas de seguridad, una mejor cobertura de pruebas o tiempos de respuesta mejorados?
  • Toma nota de las áreas problemáticas conocidas señaladas por el equipo, como puntos finales lentos, pruebas inestables o dependencias introducidas recientemente

Verificar el entorno y la configuración:

  • Confirma que el proyecto se ejecuta limpiamente en los modos de desarrollo y producción sin errores o advertencias al iniciar
  • Comprueba que la versión de Node.js en uso coincida con la fijada en .nvmrc o .node-version, y verifica que sea una versión LTS activa (las recomendaciones actuales para producción son 24.x Active LTS (Krypton) del 22-04-2025 al 30-04-2028. 22.x Maintenance LTS (Jod) del 24-04-2024 al 30-04-2027)
  • Asegúrate de que npm install o yarn install se completan sin advertencias de vulnerabilidad; ejecuta npm audit y revisa cualquier problema señalado antes de continuar
  • Confirma que las variables de entorno se cargan correctamente y que no hay secretos codificados en ninguna parte del proyecto

Revisar la documentación y el historial de git:

  • Asegúrate de que el README describa claramente cómo configurar, ejecutar, compilar y probar el proyecto
  • Revisa el historial de commits recientes para comprender qué cambió y por qué
  • Comprueba que las solicitudes de extracción (pull requests) abiertas estén fusionadas (rebased) contra la rama principal más reciente para evitar revisar código obsoleto o conflictivo

Configurar herramientas de análisis estático:

  • Confirma que ESLint utiliza eslint-plugin-n para cubrir las preocupaciones de Node.js. Aplica no-process-exit y asegúrate de que no-sync se aplique estrictamente a las rutas HTTP, permitiéndolo para scripts de inicio o herramientas CLI
  • Confirma que Prettier o un formateador equivalente esté implementado y sea consistente en todo el proyecto
  • Verifica que las comprobaciones de linting y formateo se ejecuten automáticamente en la canalización de CI/CD antes de cualquier fusión

Estructura del proyecto y organización del código

Al observar un proyecto Node.js bien organizado, puedes saber bastante sobre el equipo que lo construyó. Cuando los módulos están dispersos, las responsabilidades se difuminan y los archivos se nombran por quien tenía prisa, cada cambio futuro cuesta el doble de lo que debería.

Estructura de carpetas y límites de módulos:

  • Verifica que el proyecto siga una estructura consistente, como separar rutas, controladores, servicios y capas de acceso a datos en carpetas distintas
  • Confirma que la lógica de negocio no reside dentro de los manejadores de rutas; los manejadores de rutas deben delegar a funciones de servicio, no ejecutar consultas ni procesar datos directamente

Mala práctica:

// Manejador de ruta que hace demasiado
app.post('/orders', async (req, res) => {
  const order = await db.query('INSERT INTO orders ...', [req.body]);
  await sendEmail(order.userEmail, 'Order confirmed');
  await updateInventory(order.items);
  res.json(order);
});

Buena práctica:

// Manejador de ruta delega a un servicio
app.post('/orders', async (req, res, next) => {
  try {
    const order = await OrderService.create(req.body);
    res.status(201).json(order);
  } catch (err) {
    next(err);
  }
});

Convenciones de nomenclatura y legibilidad:

  • Utiliza camelCase para variables y funciones, PascalCase para clases y UPPER_SNAKE_CASE para constantes
  • Evita nombres cortos y crípticos como fn, tmp o data. Cada nombre de función debe describir lo que hace, no lo que es
  • Asegúrate de que los archivos se nombren de manera consistente. Es decir, si los controladores se llaman user.controller.js, todos deben seguir ese patrón, no cambiar a user-controller.js dos carpetas más allá

Uso de módulos y CommonJS vs ESM:

  • Confirma que el proyecto utiliza un sistema de módulos de manera consistente, ya sea require/CommonJS o import/ESM; mezclar los dos sin configuración es una fuente de sutiles errores en tiempo de ejecución
  • Comprueba que los módulos internos se importan usando rutas relativas y que no existen dependencias circulares. Las dependencias circulares se comportan de manera diferente según el sistema de módulos: en CommonJS producen silenciosamente undefined en tiempo de ejecución, mientras que en ESM, activan un ReferenceError fatal

Patrones asíncronos y estado del bucle de eventos

Aquí es donde los proyectos Node.js divergen más drásticamente de las aplicaciones en otros lenguajes. Node.js procesa todo en un solo hilo a través de un bucle de eventos y, según la documentación oficial de Node.js, cada callback de JavaScript debe completarse rápidamente. Bloquear ese bucle, incluso brevemente, hace que todas las solicitudes concurrentes esperen.

Evitar bloquear el bucle de eventos:

  • Busca llamadas síncronas al sistema de archivos en rutas de acceso frecuentes: fs.readFileSync, fs.writeFileSync y funciones similares bloquean completamente el bucle de eventos hasta que la operación se completa. Reemplázalas con sus equivalentes asíncronos
  • Comprueba bucles grandes de procesamiento de datos síncronos. Si una función procesa miles de registros en un solo tick, debería descargarse a un hilo de trabajo (worker thread)
  • Revisa los usos de JSON.parse y JSON.stringify en cargas útiles muy grandes, ya que son síncronos y pueden bloquear el bucle cuando los datos son lo suficientemente grandes

Mala práctica:

app.get('/config', (req, res) => {
  const config = fs.readFileSync('./config.json', 'utf8'); // bloquea todas las solicitudes
  res.json(JSON.parse(config));
});

Buena práctica (Evitar bloquear el bucle de eventos):

app.get('/config', async (req, res, next) => {
  try {
    const config = await fs.promises.readFile('./config.json', 'utf8');
    res.json(JSON.parse(config));
  } catch (err) {
    next(err);
  }
});

Manejo de Async/await y Promesas:

  • Asegúrate de que cada función asíncrona tenga un manejo de errores adecuado; un rechazo de promesa no manejado en Node.js terminará el proceso en versiones más recientes
  • Comprueba llamadas await innecesariamente secuenciales donde las operaciones podrían ejecutarse en paralelo usando Promise.all

Mala práctica:

// Secuencial — usuario y pedidos se obtienen uno tras otro
const user = await fetchUser(id);
const orders = await fetchOrders(id);

Buena práctica:

// Paralelo — ambos se obtienen simultáneamente
const [user, orders] = await Promise.all([fetchUser(id), fetchOrders(id)]);
  • Confirma que los manejadores .catch() estén adjuntos a todas las Promesas independientes que no se esperan con await
  • Busca funciones asíncronas que se llamen dentro de bucles forEach. Este patrón no espera a que el trabajo asíncrono termine y traga errores silenciosamente. Usa Promise.all con .map() en su lugar

Hilos de trabajo (Worker threads) para tareas intensivas de CPU:

  • Identifica cualquier operación intensiva de CPU, como procesamiento de imágenes, cálculos criptográficos o transformaciones de grandes datos
  • Verifica que estas tareas se descarguen a hilos de trabajo (worker threads) utilizando el módulo incorporado worker_threads de Node.js en lugar de bloquear el hilo principal

Manejo de errores

Una aplicación que maneja los errores con gracia es una aplicación en la que los operadores confían. Si la tuya no cumple ese requisito, es un incidente de producción esperando a suceder.

Propagación de errores consistente:

  • Verifica que los errores se pasen a middleware de Express usando next(err) en lugar de manejarse ad hoc dentro de cada ruta. Un manejador de errores centralizado mantiene el comportamiento consistente y registra en un solo lugar
  • Asegúrate de que las clases de error personalizadas extiendan el objeto Error incorporado y lleven un mensaje y un código de estado significativos
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
  }
}

Distingue entre errores operativos y de programación:

  • Los errores operativos son esperados: un usuario envía datos no válidos, la conexión a una base de datos expira, una API de terceros devuelve un 503. Estos deben ser capturados, registrados y devueltos al cliente con una respuesta significativa
  • Los errores de programación son bugs: acceder a una propiedad de undefined, llamar a una función que no existe. Estos deberían bloquear el proceso y ser capturados por un gestor de procesos como PM2 o una política de reinicio de contenedor
  • Confirma que la base de código no traga errores de programación silenciosamente en bloques try/catch con un cuerpo catch vacío

Rechazos no manejados y excepciones no capturadas:

  • Verifica que la aplicación registre manejadores tanto para process.on(‘unhandledRejection’) como para process.on(‘uncaughtException’) para registrar el error antes de salir, en lugar de fallar sin dejar rastro
  • Asegúrate de que la aplicación intente un apagado ordenado (cerrando conexiones del servidor y pools de bases de datos) antes de llamar a process.exit(1) para evitar perder solicitudes en curso
process.on('unhandledRejection', (reason) => {
  logger.error('Unhandled rejection:', reason);
  process.exit(1);
});

Mejores prácticas de seguridad

La verdad es que los problemas de seguridad de Node.js son una amenaza real. Por lo tanto, debes planificarlo desde el primer día de trabajo en el proyecto. Solo en 2024, se encontraron paquetes de alto perfil, incluidos web3-utils (CVE-2024-21505) y dset (CVE-2024-21529), que contenían vulnerabilidades de contaminación de prototipos que podrían conducir a la ejecución remota de código. El tamaño del ecosistema npm es su mayor fortaleza y una de sus superficies de riesgo más significativas.

Prevención de contaminación de prototipos:

  • Busca cualquier código que fusione o clone objetos proporcionados por el usuario sin validación, como funciones de fusión recursiva y utilidades de clonación profunda sobre entradas no confiables, que son un punto de entrada común para la contaminación de prototipos
  • Asegúrate de que los objetos que manejan la entrada del usuario se creen con Object.create(null) cuando sea apropiado, eliminando completamente la cadena de prototipos
  • Verifica que las cargas útiles JSON de fuentes externas se validen contra un esquema antes de procesarse

Validación de entrada y prevención de inyección:

  • Confirma que todos los datos de solicitud entrantes, parámetros de consulta, encabezados y campos del cuerpo se validen antes de usarse. Bibliotecas como zod, joi o express-validator son herramientas estándar para esto
  • Comprueba todas las consultas a bases de datos en busca de entradas parametrizadas, ya que las consultas concatenadas de cadenas son una vía directa para la inyección de SQL
  • Revisa cualquier uso de eval(), new Function() o child_process.exec() con entrada dinámica. Estos deben tratarse como vulnerabilidades críticas a menos que haya una justificación explícita y bien documentada

Encabezados de seguridad HTTP y HTTPS:

  • Confirma que helmet o un middleware equivalente esté implementado para establecer encabezados HTTP relevantes para la seguridad, incluidos Content-Security-Policy, X-Frame-Options y Strict-Transport-Security
  • Verifica que la aplicación aplique HTTPS en producción y no acepte solicitudes HTTP sin cifrar para rutas autenticadas
  • Comprueba que la configuración de cookies incluya las marcas Secure, HttpOnly y SameSite

Limitación de velocidad y protección contra denegación de servicio:

  • Verifica que los puntos finales de la API, especialmente los puntos finales de autenticación, tengan limitación de velocidad implementada utilizando middleware como express-rate-limit
  • Busca cualquier expresión regular aplicada a la entrada del usuario; los patrones ineficientes pueden ser explotados para consumir tiempo de CPU en lo que se conoce como un ataque ReDoS

Secretos y autenticación:

  • Confirma que los secretos JWT, las claves de API y las credenciales de la base de datos nunca se codifiquen de forma rígida en archivos de origen ni se carguen en el control de versiones
  • Revisa la lógica de validación de tokens y asegúrate de que se apliquen restricciones de algoritmos para JWT para evitar la vulnerabilidad del algoritmo ‘none’
  • Verifica que el hash de contraseñas utilice bcrypt, argon2 o scrypt, no MD5, SHA-1 o SHA-256 simple sin sal

Optimización del rendimiento

Una aplicación Node.js que se ejecuta rápidamente en desarrollo a menudo se comporta de manera muy diferente bajo carga de producción. Los problemas de rendimiento se acumulan rápidamente cuando tienes muchas conexiones concurrentes.

Eficiencia de las consultas a la base de datos:

  • Revisa todas las consultas a bases de datos en busca de patrones N+1. Si una ruta realiza una consulta para obtener una lista de registros y luego una consulta separada por registro para obtener datos relacionados, se ralentizará drásticamente a escala
  • Confirma que existen índices de base de datos para todos los campos utilizados en las cláusulas WHERE, JOINs y expresiones ORDER BY
  • Comprueba las consultas sin límites, ya que cualquier consulta que pueda devolver un número arbitrario de filas sin paginación es un futuro problema de memoria y tiempo de respuesta

Caché:

  • Identifica operaciones costosas, como consultas complejas a bases de datos o llamadas a API externas, que podrían beneficiarse del almacenamiento en caché
  • Verifica que exista una capa de caché, como Redis o una caché en memoria, para datos accedidos con frecuencia y que cambian lentamente
  • Comprueba que exista lógica de invalidación de caché, porque una caché que crece indefinidamente o sirve datos obsoletos es peor que ninguna caché

Agrupación de conexiones y gestión de recursos:

  • Confirma que las conexiones a la base de datos utilizan un pool en lugar de abrir una nueva conexión por solicitud. Este es un error común que hace que las aplicaciones Node.js agoten los límites de conexión de la base de datos bajo carga
  • Comprueba que los clientes HTTP y las conexiones de servicios externos se reutilicen en lugar de crearse por solicitud

Streaming para datos grandes:

  • Revisa cualquier punto final que lea archivos grandes o conjuntos de resultados de bases de datos en memoria antes de enviarlos al cliente. Estos deberían usar flujos (streams) de Node.js para canalizar datos incrementalmente en lugar de almacenar todo en búfer primero

Gestión de dependencias

Hay más de 2 millones de paquetes en el registro npm. A través de ellos, cada proyecto Node.js tiene un árbol de dependencias que se extiende mucho más allá de lo que cualquier equipo controla directamente. Por lo tanto, el árbol en sí es una superficie de riesgo que debes gestionar eficazmente.

Mantener las dependencias actualizadas y mínimas:

  • Ejecuta npm audit y verifica que todas las vulnerabilidades altas y críticas se hayan resuelto o tengan una excepción explícita y documentada
  • Revisa package.json en busca de dependencias que ya no se utilicen, ya que los paquetes no utilizados aumentan el tiempo de instalación y la superficie de ataque sin proporcionar ningún valor
  • Comprueba que los rangos de versiones en package.json no sean excesivamente permisivos. Si bien Semantic Versioning dicta que las actualizaciones menores deben ser compatibles con versiones anteriores, un rango caret como ^4.0.0 puede introducir cambios disruptivos en la práctica si los autores de paquetes violan el estándar; package-lock.json o yarn.lock deben ser cargados para garantizar instalaciones reproducibles

Evaluación de paquetes de terceros:

  • Para cualquier dependencia recién agregada, verifica su volumen de descargas npm, la fecha de la última publicación y el número de avisos de seguridad abiertos
  • Prefiere paquetes que se mantengan activamente y tengan un historial de propiedad claro, ya que un paquete publicado por última vez hace cuatro años sin actividad es un riesgo para la cadena de suministro
  • Verifica que las bibliotecas de utilidades pesadas no se importen solo para una función; lodash importado en su totalidad agrega kilobytes donde un solo método nativo haría el mismo trabajo

Pruebas y cobertura de código

Las pruebas no son burocracia, sino el mecanismo mediante el cual un equipo comunica lo que se supone que debe hacer el código y la única forma confiable de saber si un cambio rompió algo.

Cobertura de pruebas unitarias y de integración:

  • Verifica que todas las funciones de servicio y módulos de utilidad tengan pruebas unitarias que cubran el camino feliz, casos extremos y condiciones de error
  • Confirma que los manejadores de rutas tengan pruebas de integración que validen el ciclo completo de solicitud-respuesta, incluido el middleware de autenticación
  • Comprueba que el conjunto de pruebas se ejecute sin llamadas de red o base de datos, a menos que esas llamadas sean pruebas de integración intencionales, ya que las pruebas unitarias deben usar mocks para dependencias externas

Calidad de las pruebas, no solo números de cobertura:

  • Revisa si las pruebas verifican resultados significativos en lugar de simplemente confirmar que se llamó a una función; una prueba que solo verifica toHaveBeenCalled sin validar el resultado no protege el comportamiento
  • Asegúrate de que los nombres de las pruebas describan el comportamiento que se está probando, por ejemplo, it(‘returns 401 when the token is expired’) en lugar de it(‘handles auth’)
  • Confirma que la canalización de CI falla ante fallos de prueba y que no se omiten pruebas con it.skip o xit sin una razón documentada

Configuración del entorno y secretos

Cuando ves la brecha entre cómo se comporta una aplicación Node.js en desarrollo y en producción, estás viendo problemas que se originan en la configuración.

Gestión de variables de entorno:

  • Confirma que toda la configuración específica del entorno, incluidas las URL de bases de datos, las claves de API, las banderas de funciones y los números de puerto, provienen de variables de entorno en lugar de valores codificados de forma rígida
  • Verifica que exista un archivo .env.example en el repositorio que documente todas las variables requeridas sin sus valores reales. El archivo .env real debe estar en .gitignore
  • Comprueba que la aplicación falle de forma audible al iniciar si faltan variables de entorno requeridas, en lugar de ejecutarse en un estado parcialmente configurado que produce errores sutiles más adelante
// Valida las variables de entorno requeridas al inicio
const required = ['DATABASE_URL', 'JWT_SECRET', 'PORT'];
for (const key of required) {
  if (!process.env[key]) {
    throw new Error(`Falta la variable de entorno requerida: ${key}`);
  }
}

Configuración de producción vs. desarrollo:

  • Verifica que NODE_ENV esté configurado correctamente en todos los entornos de implementación y que la aplicación se comporte apropiadamente en cada modo, por ejemplo, mensajes de error detallados solo en desarrollo
  • Confirma que las herramientas de depuración y el registro detallado estén deshabilitados en las compilaciones de producción

Registro y observabilidad

Imagina que recibes una alerta a las 2 AM de que algo salió mal. La diferencia entre una solución de 15 minutos para este problema y una investigación de horas estará determinada por la calidad de tus registros.

Registro estructurado:

  • Verifica que la aplicación utiliza una biblioteca de registro estructurado como pino o winston en lugar de declaraciones console.log dispersas por la base de código. Los registros estructurados son buscables y compatibles con herramientas de agregación de registros
  • Asegúrate de que las entradas de registro incluyan una marca de tiempo, nivel de registro, ID de solicitud y suficiente contexto para comprender lo que sucedió sin tener que adivinar

Niveles de registro significativos:

  • Confirma que los niveles de registro se utilizan correctamente: info para operaciones normales, warn para condiciones inesperadas pero no críticas, error para fallos que requieren atención y debug para datos de diagnóstico detallados que nunca deberían aparecer en producción
  • Busca llamadas a console.log dejadas en el código de producción. Son una señal de que el registro no se configuró correctamente y que es posible que información importante se esté enviando al lugar equivocado

Seguimiento de solicitudes y comprobaciones de estado:

  • Verifica que la aplicación asigne un ID de solicitud único a cada solicitud entrante y lo propague a través de todas las entradas de registro para esa solicitud. Esto hace posible rastrear la solicitud de un usuario a través de un sistema distribuido
  • Confirma que existe un punto final de comprobación de estado y que devuelve una respuesta significativa que indica si la aplicación y sus dependencias están operativas

Revisión de código Node.js con Redwerk

Repasar esta lista de verificación por tu cuenta es un buen comienzo, pero el siguiente paso es que un equipo externo la ejecute por ti. En Redwerk, hemos estado creando y revisando aplicaciones durante más de dos décadas. Nuestro equipo cubre la imagen completa: arquitectura, patrones asíncronos, seguridad, rendimiento y pruebas. Señalamos lo que está roto, explicamos por qué importa y te damos un camino claro para solucionarlo.

Veamos cómo funciona esto en la práctica a través de nuestro estudio de caso para Kooky, un sistema de tazas inteligentes reutilizables que construimos desde cero. Es una plataforma para el mercado suizo que se ejecuta en un backend en tiempo real y basado en eventos. Actualmente, maneja el seguimiento de tazas basado en QR, actualizaciones de cuentas en vivo y procesamiento instantáneo de depósitos. Opera en asociación con los Ferrocarriles Federales Suizos, Valora y Coop.

Para mantener ese sistema estable bajo carga concurrente, tuvimos que acertar cada elemento de esta lista desde el primer día, incluyendo:

  • Patrones asíncronos que nunca agotan el bucle de eventos
  • Limitación de velocidad en los puntos finales de pago
  • Registro estructurado que hace que los incidentes de producción sean rastreables
  • Árbol de dependencias que se audita en cada lanzamiento

Con todo eso implementado, Kooky se convirtió en la startup de tecnología verde número uno de Suiza, y una buena arquitectura tuvo mucho que ver.

Más de 50 empresas han confiado en nosotros para crear o auditar sus productos desde cero. Y ya sea que su proyecto Node.js sea una API de startup o una plataforma empresarial, nuestra experiencia en desarrollo Node.js significa que sabemos exactamente dónde suelen esconderse los problemas.

Si su base de código necesita un examen más detenido, contáctenos y nosotros nos encargaremos del resto.

Vea la diferencia que puede marcar una revisión de código: cómo identificamos más de 80 mejoras y riesgos de seguridad para un mercado móvil

Este campo es obligatorio. no es un correo electrónico comercial