Programación Web · Capítulo 13
Node.js y el Backend: Cómo Funciona el Servidor
Descubre cómo construir servidores web con JavaScript usando Node.js y Express, el framework más popular del ecosistema.
1. Frontend vs Backend: La División del Trabajo
En el desarrollo web moderno, hay una clara separación de responsabilidades entre lo que ocurre en el navegador del usuario y lo que ocurre en los servidores remotos.
| Aspecto | Frontend | Backend |
| Dónde corre | Navegador del usuario | Servidor remoto |
| Tecnologías | HTML, CSS, JavaScript, React | Node.js, Python, Java, PHP |
| Responsabilidad | Interfaz visual, interacción | Lógica de negocio, base de datos |
| El usuario lo ve | Sí | No directamente |
| Seguridad | Nunca guardar secretos aquí | Claves, contraseñas, lógica sensible |
2. Node.js: JavaScript en el Servidor
Node.js es un entorno de ejecución de JavaScript construido sobre el motor V8 de Chrome. Permite ejecutar JavaScript fuera del navegador — en servidores, herramientas de línea de comandos y más. Su característica clave es la I/O no bloqueante: puede manejar miles de conexiones simultáneas sin crear un hilo por cada una.
npm y package.json
# Inicializar un proyecto nuevo
npm init -y
# Instalar una dependencia
npm install express
# Instalar dependencia de desarrollo (solo para desarrollo)
npm install --save-dev nodemon
# Ejecutar scripts definidos en package.json
npm run dev
npm start
// package.json generado
{
"name": "mi-servidor",
"version": "1.0.0",
"description": "API REST con Express",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"dotenv": "^16.0.3"
},
"devDependencies": {
"nodemon": "^3.0.0"
}
}
3. Servidor HTTP Básico con Node.js
// servidor-basico.js
const http = require("http");
const servidor = http.createServer((req, res) => {
// req = solicitud del cliente
// res = respuesta que enviamos
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ mensaje: "¡Hola desde Node.js!" }));
});
servidor.listen(3000, () => {
console.log("Servidor corriendo en http://localhost:3000");
});
4. Express.js: El Framework Esencial
Express simplifica enormemente el trabajo con Node.js. Añade routing, middleware, y utilidades para manejar requests y responses de forma cómoda.
// index.js — servidor Express completo
const express = require("express");
const cors = require("cors");
require("dotenv").config();
const app = express();
const PORT = process.env.PORT || 3000;
// ── Middleware ────────────────────────────────────────────
app.use(cors()); // Permitir solicitudes desde otros dominios
app.use(express.json()); // Parsear body en formato JSON
app.use(express.urlencoded({ extended: true })); // Parsear formularios HTML
// Middleware de logging personalizado
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
next(); // IMPORTANTE: pasar al siguiente middleware/ruta
});
// ── Datos en memoria (en producción usarías una BD) ───────
let productos = [
{ id: 1, nombre: "Laptop", precio: 999, stock: 15 },
{ id: 2, nombre: "Mouse", precio: 45, stock: 80 },
{ id: 3, nombre: "Teclado", precio: 75, stock: 40 }
];
// ── Rutas ─────────────────────────────────────────────────
// GET: obtener todos los productos
app.get("/api/productos", (req, res) => {
const { categoria, minPrecio } = req.query; // Query params: ?categoria=tech
let resultado = [...productos];
if (minPrecio) {
resultado = resultado.filter(p => p.precio >= Number(minPrecio));
}
res.json({
total: resultado.length,
datos: resultado
});
});
// GET: obtener un producto por ID
app.get("/api/productos/:id", (req, res) => {
const id = parseInt(req.params.id); // Parámetros de ruta: /api/productos/1
const producto = productos.find(p => p.id === id);
if (!producto) {
return res.status(404).json({ error: "Producto no encontrado" });
}
res.json(producto);
});
// POST: crear un producto
app.post("/api/productos", (req, res) => {
const { nombre, precio, stock } = req.body;
// Validación básica
if (!nombre || !precio) {
return res.status(400).json({ error: "nombre y precio son obligatorios" });
}
const nuevoProducto = {
id: productos.length + 1,
nombre,
precio: Number(precio),
stock: stock || 0
};
productos.push(nuevoProducto);
res.status(201).json(nuevoProducto);
});
// PUT: actualizar un producto completo
app.put("/api/productos/:id", (req, res) => {
const id = parseInt(req.params.id);
const index = productos.findIndex(p => p.id === id);
if (index === -1) {
return res.status(404).json({ error: "Producto no encontrado" });
}
productos[index] = { id, ...req.body };
res.json(productos[index]);
});
// DELETE: eliminar un producto
app.delete("/api/productos/:id", (req, res) => {
const id = parseInt(req.params.id);
const index = productos.findIndex(p => p.id === id);
if (index === -1) {
return res.status(404).json({ error: "Producto no encontrado" });
}
const eliminado = productos.splice(index, 1)[0];
res.json({ mensaje: "Producto eliminado", producto: eliminado });
});
// Manejo de rutas no encontradas
app.use((req, res) => {
res.status(404).json({ error: "Ruta no encontrada" });
});
// ── Iniciar servidor ──────────────────────────────────────
app.listen(PORT, () => {
console.log(`Servidor corriendo en http://localhost:${PORT}`);
});
5. Variables de Entorno con .env
# .env — NUNCA subir este archivo a Git
PORT=3000
DATABASE_URL=mysql://usuario:contraseña@localhost:3306/mibasedatos
JWT_SECRET=mi_clave_secreta_muy_larga_y_aleatoria
NODE_ENV=development
// Usando variables de entorno en el código
require("dotenv").config();
const puerto = process.env.PORT;
const dbUrl = process.env.DATABASE_URL;
const esProduccion = process.env.NODE_ENV === "production";
if (!process.env.JWT_SECRET) {
throw new Error("JWT_SECRET no está configurado");
}
6. Estructura de Proyecto Express Escalable
mi-api/
├── src/
│ ├── routes/
│ │ ├── productos.js # Rutas de productos
│ │ └── usuarios.js # Rutas de usuarios
│ ├── controllers/
│ │ ├── productosController.js
│ │ └── usuariosController.js
│ ├── middleware/
│ │ ├── auth.js # Verificar JWT
│ │ └── validacion.js # Validar inputs
│ ├── models/
│ │ └── Producto.js # Interacción con BD
│ └── config/
│ └── database.js # Conexión a BD
├── .env
├── .gitignore
├── index.js # Punto de entrada
└── package.json
// src/routes/productos.js — separar rutas en archivos
const express = require("express");
const router = express.Router();
const { listar, obtener, crear, actualizar, eliminar } = require("../controllers/productosController");
const { verificarToken } = require("../middleware/auth");
router.get("/", listar);
router.get("/:id", obtener);
router.post("/", verificarToken, crear); // Requiere autenticación
router.put("/:id", verificarToken, actualizar);
router.delete("/:id", verificarToken, eliminar);
module.exports = router;
// En index.js:
// app.use("/api/productos", require("./src/routes/productos"));
Resumen del Capítulo
- El backend maneja la lógica de negocio, base de datos y seguridad; el frontend maneja la interfaz visual. Nunca pongas secretos en el frontend.
- Node.js ejecuta JavaScript en el servidor con I/O no bloqueante, permitiendo manejar miles de conexiones simultáneas de forma eficiente.
- Express.js añade routing y middleware a Node.js; el middleware se ejecuta en cadena y debe llamar
next() para continuar.
- Los parámetros de ruta (
:id) se leen en req.params; los query params (?key=val) en req.query; el cuerpo JSON en req.body.
- Las variables de entorno (
.env) separan la configuración del código; nunca subas el archivo .env a Git.
- Organiza el código en capas: routes (URLs), controllers (lógica), models (datos) para proyectos mantenibles.
- Siempre responde con códigos HTTP apropiados: 200 (OK), 201 (creado), 400 (error del cliente), 404 (no encontrado), 500 (error del servidor).