Fundamentos de Node.js: Guía Completa

Módulos nativos de Node.js

Node.js viene con una colección de módulos nativos para realizar tareas comunes sin necesidad de instalar dependencias adicionales. Algunos de los más utilizados son:

Módulo fs: Manipulación de archivos

El módulo fs permite trabajar con el sistema de archivos. Ejemplo básico para leer y escribir archivos:

javascript
const fs = require('fs');

// Leer un archivo
fs.readFile('archivo.txt', 'utf8', (err, data) => {
  if (err) {
      console.error('Error al leer el archivo:', err);
      return;
  }
  console.log('Contenido del archivo:', data);
});

// Escribir en un archivo
fs.writeFile('archivo.txt', 'Hola, Node.js!', (err) => {
  if (err) {
      console.error('Error al escribir en el archivo:', err);
      return;
  }
  console.log('Archivo escrito correctamente.');
});

Módulo http: Crear un servidor HTTP

Con el módulo http, puedes crear un servidor básico:

javascript
const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hola, Mundo desde Node.js!');
});

server.listen(3000, () => {
  console.log('Servidor ejecutándose en http://localhost:3000');
});

Módulo path: Manejo de rutas

El módulo path simplifica el manejo de rutas de archivos:

javascript
const path = require('path');

const rutaAbsoluta = path.resolve('archivo.txt');
console.log('Ruta absoluta:', rutaAbsoluta);

const nombreArchivo = path.basename(rutaAbsoluta);
console.log('Nombre del archivo:', nombreArchivo);

const extension = path.extname(rutaAbsoluta);
console.log('Extensión:', extension);

Módulo events: Emisión de eventos

El módulo events facilita la creación y manejo de eventos personalizados:

javascript
const EventEmitter = require('events');

const emisor = new EventEmitter();

// Escuchar un evento
emisor.on('saludo', (nombre) => {
  console.log(`¡Hola, ${nombre}!`);
});

// Emitir un evento
emisor.emit('saludo', 'Node.js');

stream: Lectura/escritura de flujos de datos

Los flujos (streams) permiten trabajar con datos de manera eficiente. Por ejemplo, para leer un archivo en partes:

javascript
const fs = require('fs');

const streamLectura = fs.createReadStream('archivo.txt', 'utf8');

streamLectura.on('data', (chunk) => {
  console.log('Datos leídos:', chunk);
});

streamLectura.on('end', () => {
  console.log('Lectura finalizada.');
});

Callbacks y Promesas


Explicación de Callbacks y el “Callback Hell”

Un callback es una función que se pasa como argumento a otra función y se ejecuta después de que la primera función termine.

Ejemplo básico:

javascript
function hacerAlgo(callback) {
  console.log('Haciendo algo...');
  callback();
}

hacerAlgo(() => {
  console.log('Hecho!');
});

Callback Hell: Cuando los callbacks se anidan en varias capas para manejar flujos complejos, el código puede volverse difícil de leer y mantener. Esto se conoce como Callback Hell o la “pirámide de la perdición”.

javascript
obtenerDatos((error, datos) => {
  if (error) {
      console.error('Error al obtener datos:', error);
  } else {
      procesarDatos(datos, (error, resultado) => {
          if (error) {
              console.error('Error al procesar datos:', error);
          } else {
              guardarDatos(resultado, (error) => {
                  if (error) {
                      console.error('Error al guardar datos:', error);
                  } else {
                      console.log('Datos guardados correctamente');
                  }
              });
          }
      });
  }
});

Como puedes ver, múltiples niveles de callbacks anidados pueden hacer que el código sea más complejo y menos legible, dificultando su depuración y mantenimiento.

Soluciones al Callback Hell

Para evitar este problema, se pueden utilizar técnicas modernas como:

  • Promesas
  • Async/Await
  • Manejo de librerías como axios

Uso de Promesas


Las promesas en JavaScript son una forma moderna y más limpia de manejar tareas asincrónicas, evitando el problema del Callback Hell. Permiten ejecutar código cuando una operación se completa correctamente (resolve) o cuando ocurre un error (reject).

Ejemplo básico:

javascript
function obtenerDatos() {
  return new Promise((resolve, reject) => {
      const exito = true; // Cambia esto a `false` para simular un error.

      setTimeout(() => {
          if (exito) {
              resolve('Datos obtenidos correctamente');
          } else {
              reject('Error al obtener los datos');
          }
      }, 2000); // Simula una tarea asincrónica con setTimeout
  });
}

obtenerDatos()
  .then((resultado) => {
      console.log(resultado); // 'Datos obtenidos correctamente'
  })
  .catch((error) => {
      console.error(error); // 'Error al obtener los datos' (si exito es false)
  });

Introducción a async/await

async/await es una forma aún más sencilla y legible de manejar tareas asincrónicas en JavaScript, basada en Promesas. Permite escribir código que parece síncrono mientras maneja operaciones asincrónicas.

javascript
// Función que simula obtener datos de manera asincrónica
function obtenerDatos() {
  return new Promise((resolve, reject) => {
      setTimeout(() => {
          resolve('Datos obtenidos correctamente');
      }, 2000); // Simula un retraso de 2 segundos
  });
}

// Función que usa async/await para esperar la respuesta
async function ejecutar() {
  try {
      console.log('Obteniendo datos...');
      const resultado = await obtenerDatos(); // Espera a que la promesa se resuelva
      console.log(resultado); // 'Datos obtenidos correctamente'
  } catch (error) {
      console.error('Error:', error); // Maneja errores
  }
}

ejecutar();
  Explicación del Código

  • async: Declara la función ejecutar como asincrónica, lo que permite usar await dentro de ella.
  • await: Hace que la ejecución espere hasta que la promesa obtenerDatos se resuelva o rechace.
  • try/catch: Captura y maneja errores que puedan ocurrir durante la operación asincrónica.