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, RedCaracterí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
| Aspecto | Módulo HTTP | Express |
|---|---|---|
| Routing | Manual (if/else) | app.get(), app.post(), etc. |
| Parseo de body | Manual | express.json() |
| Archivos estáticos | Manual | express.static() |
| Middlewares | Manual | app.use() |
| Manejo de errores | Manual | Middleware de errores |
| Productividad | Baja | Alta |
Express es una abstracción delgada sobre el módulo HTTP nativo. No oculta nada, solo lo hace más ergonómico.