FullStackJS Camp
Módulo 6·teoria·3h
Objetivos de aprendizaje
  • Entender el event loop y la asincronía no bloqueante de Node.js
  • Crear un servidor HTTP con el módulo nativo de Node.js
  • Comprender la diferencia entre CommonJS y ESModules
  • Usar el módulo fs para leer archivos con callbacks y async/await
  • Crear una API REST básica con Express

Node.js y Express: Fundamentos

¿Qué es Node.js?

Node.js es un entorno de ejecución de JavaScript del lado del servidor. No es un lenguaje ni un framework: es un runtime que permite ejecutar JS fuera del navegador.

code
Navegador:  JavaScript → Motor V8 → Resultados en el DOM
Node.js:    JavaScript → Motor V8 → Acceso a SO, FS, Red

Características clave

  • Single-threaded: Un solo hilo de ejecución principal
  • Non-blocking I/O: Las operaciones de entrada/salida no bloquean el hilo principal
  • Event-driven: Basado en eventos y callbacks
  • V8 Engine: El mismo motor que usa Chrome (compilación JIT de JS a código nativo)

El Event Loop

El event loop es el mecanismo que permite a Node.js ser no bloqueante con un solo hilo:

code
┌───────────────────────────┐
   │        Call Stack         │ ← Ejecuta código JS
   └──────────┬────────────────┘
              │ vacía
   ┌──────────▼────────────────┐
   │       Event Queue         │ ← Callbacks listos para ejecutar
   └──────────┬────────────────┘

   ┌──────────▼────────────────┐
   │    Libuv (I/O, timers)    │ ← Operaciones asíncronas (FS, red...)
   └───────────────────────────┘
javascript
console.log("1. Inicio");

setTimeout(() => {
  console.log("3. setTimeout (después del event loop)");
}, 0);

console.log("2. Fin del código síncrono");

// Output:
// 1. Inicio
// 2. Fin del código síncrono
// 3. setTimeout (después del event loop)

Módulo HTTP nativo

Node.js incluye un módulo HTTP para crear servidores sin instalar nada:

javascript
// server-nativo.js
import http from "http";

const server = http.createServer((req, res) => {
  const url = req.url;
  const method = req.method;

  // Enrutar manualmente
  if (url === "/" && method === "GET") {
    res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
    res.end("<h1>Bienvenido a Node.js</h1>");
    return;
  }

  if (url === "/api/saludo" && method === "GET") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ mensaje: "Hola desde Node.js" }));
    return;
  }

  // 404
  res.writeHead(404, { "Content-Type": "text/plain" });
  res.end("Ruta no encontrada");
});

server.listen(3000, () => {
  console.log("Servidor corriendo en http://localhost:3000");
});

Como ves, con el módulo nativo tienes que hacer el routing manualmente. Para esto existe Express.

CommonJS vs ESModules

Node.js soporta dos sistemas de módulos:

javascript
// CommonJS (legacy, .cjs o sin "type": "module")
const express = require("express");
const { readFile } = require("fs");
module.exports = { saludar };

// ESModules (moderno, "type": "module" en package.json)
import express from "express";
import { readFile } from "fs";
export { saludar };
export default saludar;

Un problema habitual al usar ESModules en Node.js es que __dirname no existe. Usa este patrón:

javascript
// __dirname en ESModules
import { fileURLToPath } from "url";
import path from "path";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Ahora puedes usar __dirname normalmente
const rutaDB = path.join(__dirname, "data", "tareas.json");

Módulo FS: Sistema de Archivos

javascript
import fs from "fs";
import { readFile, writeFile } from "fs/promises";
import path from "path";

// --- Lectura síncrona (bloquea el hilo) ---
const contenido = fs.readFileSync("datos.txt", "utf-8");
console.log(contenido);

// --- Lectura con callback (no bloquea) ---
fs.readFile("datos.txt", "utf-8", (err, data) => {
  if (err) throw err;
  console.log(data);
});

// --- Lectura con async/await (recomendado) ---
async function leerArchivo() {
  try {
    const data = await readFile("datos.txt", "utf-8");
    console.log(data);
  } catch (err) {
    console.error("Error al leer:", err.message);
  }
}

// --- Escritura async/await ---
async function guardarJSON(datos) {
  await writeFile(
    "datos.json",
    JSON.stringify(datos, null, 2),
    "utf-8"
  );
}

Express: simplificando todo

Express resuelve el problema del routing manual del módulo HTTP:

javascript
// server-express.js
import express from "express";

const app = express();
const PORT = process.env.PORT ?? 3000;

// Middleware para parsear JSON
app.use(express.json());

// Datos en memoria (array simple)
const usuarios = [
  { id: 1, nombre: "Ana García", email: "ana@email.com" },
  { id: 2, nombre: "Pedro López", email: "pedro@email.com" },
];

// GET todos
app.get("/api/usuarios", (req, res) => {
  res.json(usuarios);
});

// GET por ID
app.get("/api/usuarios/:id", (req, res) => {
  const usuario = usuarios.find((u) => u.id === Number(req.params.id));
  if (!usuario) return res.status(404).json({ error: "Usuario no encontrado" });
  res.json(usuario);
});

// POST crear
app.post("/api/usuarios", (req, res) => {
  const { nombre, email } = req.body;
  if (!nombre || !email) {
    return res.status(400).json({ error: "Nombre y email son requeridos" });
  }
  const nuevo = { id: Date.now(), nombre, email };
  usuarios.push(nuevo);
  res.status(201).json(nuevo);
});

// Ruta 404 genérica
app.use((req, res) => {
  res.status(404).json({ error: `Ruta ${req.url} no encontrada` });
});

app.listen(PORT, () => {
  console.log(`🚀 Servidor en http://localhost:${PORT}`);
});

Comparativa: HTTP nativo vs Express

AspectoMódulo HTTPExpress
RoutingManual (if/else)app.get(), app.post(), etc.
Parseo de bodyManualexpress.json()
Archivos estáticosManualexpress.static()
MiddlewaresManualapp.use()
Manejo de erroresManualMiddleware de errores
ProductividadBajaAlta

Express es una abstracción delgada sobre el módulo HTTP nativo. No oculta nada, solo lo hace más ergonómico.