Node
Tutorial Node: Estructura de carpetas
Descubre cómo implementar la arquitectura MVC en Node.js para mejorar la organización y mantenibilidad de tus aplicaciones.
Aprende Node GRATIS y certifícateMVC (Model-View-Controller) aplicado a Node.js nativo
La arquitectura MVC en entornos Node.js nativos permite organizar la aplicación en tres bloques que colaboran de forma modular. El controlador recibe las peticiones y decide qué acción ejecutar, el modelo gestiona la información y la vista se encarga de presentar los resultados al cliente. Este enfoque facilita la mantenibilidad y la claridad en proyectos de tamaño medio y grande.
En el modelo, se define cómo se accede y manipula la información. Por ejemplo, si se necesita almacenar y consultar datos en un sistema de archivos, puede usarse el módulo fs
, mientras que para peticiones HTTP a servicios externos se recurre al módulo http
o a otras librerías nativas. Se recomienda mantener las funciones de lectura y escritura fuera de los controladores para que sea más escalable el código.
La vista se encarga de dar forma a la respuesta que llega al navegador u otra aplicación cliente. Puede ser HTML, JSON o cualquier otro formato. En un proyecto basado en Node.js sin frameworks adicionales, a menudo la vista es una concatenación de cadenas o plantillas simples que reflejan la información proveniente del modelo. Si se devuelven datos en formato JSON, la vista es esencialmente la serialización de objetos que facilitan la interoperabilidad con distintos clientes.
Por último, el controlador es el punto de entrada principal para cada petición. Empleando el módulo nativo http
, un ejemplo simplificado podría lucir así:
import { createServer } from "node:http";
// Controlador que gestiona la petición
function mainController(req, res) {
if (req.url === "/datos" && req.method === "GET") {
const data = getModelData(); // Llama a un método del modelo
const output = createView(data); // Genera la vista
res.writeHead(200, { "Content-Type": "application/json" });
res.end(output);
} else {
res.writeHead(404);
res.end("Recurso no encontrado");
}
}
// Ejemplo de funciones del modelo y la vista
function getModelData() {
return { mensaje: "Saludo desde el modelo" };
}
function createView(data) {
return JSON.stringify(data);
}
// Configuración del servidor
const server = createServer(mainController);
server.listen(3000, () => console.log("Servidor en marcha en el puerto http://localhost:3000"));
Este ejemplo muestra cómo la lógica de cada parte se mantiene separada. El controlador decide qué datos del modelo obtener y luego invoca a la vista para formatearlos. De este modo, el mantenimiento del proyecto resulta más flexible y la aplicación conserva una estructura clara que facilita la detección de posibles mejoras o errores.
Separación de responsabilidad (controlador vs lógica de negocio)
Al estructurar un proyecto de Node.js, es recomendable que el controlador solo gestione las interacciones directas con la petición y la respuesta. Este enfoque permite que las validaciones iniciales y el enrutamiento queden concentrados en un lugar concreto, mientras que la lógica de negocio reside en módulos o capas más específicas. De esta manera, el controlador evita sobrecargas y promueve la reutilización del código.
Resulta útil contar con un servicio o capa independiente que encapsule toda la lógica relacionada con la manipulación de datos y las reglas de negocio. Así, el controlador se limita a delegar la responsabilidad a ese servicio y a procesar la información de entrada para enviarla de forma coherente. Este patrón mejora la mantenibilidad y facilita la realización de pruebas automatizadas.
Cuando los proyectos crecen, se suele crear un directorio dedicado a alojar ficheros de servicios que gestionen operaciones complejas. Entre estas operaciones se incluyen cálculo de comisiones, manejo de estados de usuario y validación en profundidad. El controlador, por su parte, se concentra en traducir los parámetros de la URL o el cuerpo de la petición para que el servicio efectúe la tarea correspondiente.
En el ejemplo siguiente, el controlador deriva todo el tratamiento a la capa de negocio, limitándose a controlar la petición y a emitir una respuesta adecuada:
import { createUser } from "./user.service.mjs";
// controlador: user.controller.mjs
export async function userController(req, res) {
try {
if (req.url === "/users" && req.method === "POST") {
const requestBody = await parseRequestBody(req);
const newUser = await createUser(requestBody);
res.writeHead(201, { "Content-Type": "application/json" });
res.end(JSON.stringify(newUser));
}
} catch {
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("Error interno");
}
}
// Función para analizar el cuerpo de la solicitud
async function parseRequestBody(req) {
let body = "";
req.on("data", (chunk) => {
body += chunk.toString();
});
req.on("end", () => {
const parsedBody = JSON.parse(body);
return parsedBody;
});
}
// capa de negocio: user.service.mjs
export async function createUser(userData) {
// Aquí se ubica la lógica de negocio, por ejemplo validaciones y persistencia
return { id: 1, ...userData };
}
Cada controlador puede servirse de múltiples capas o servicios, asegurando que solo maneje la comunicación entre el cliente y la lógica real. Este reparto de roles mejora la coherencia del proyecto y agiliza la integración de nuevas funcionalidades.
Uso de servicios o capas de dominio para aislar la lógica
En muchos proyectos de Node resulta efectivo utilizar un enfoque basado en servicios o capas de dominio como intermediarios entre los controladores y la fuente de datos. Esta capa concentra todas las operaciones y reglas específicas de la aplicación, permitiendo que los controladores se mantengan ligeros y centrados en la gestión de peticiones y respuestas.
Un servicio o capa de dominio puede englobar validaciones de integridad, interacciones con múltiples fuentes de información y reglas empresariales que no deben mezclarse con la inmediatez del controlador. Este aislamiento facilita la actualización o sustitución de la base de datos o de la lógica reutilizable sin afectar a otras partes del proyecto.
Para estructurar el código, se acostumbra a dedicar un directorio exclusivo a los servicios, separados por áreas de negocio. Además de clarificar la responsabilidad de cada archivo, este diseño promueve la escalabilidad y la colaboración en equipo, puesto que cada cambio en la lógica de un servicio queda claramente delimitado.
A modo de ejemplo, si el proyecto debe manejar la facturación de usuarios, se crearía un fichero como "billingService.mjs" con las funciones de cálculo, descuentos y validaciones. Luego, desde cualquier controlador, bastaría con invocar dichas funciones sin exponer los detalles internos:
// billingService.mjs
export function calcularTotal(productos) {
// Operaciones específicas de cálculo de importe
return productos.reduce((acum, prod) => acum + prod.precio, 0);
}
export function aplicarDescuento(total, porcentaje) {
return total - total * (porcentaje / 100);
}
Con esta aproximación, el desarrollo de nuevas características resulta más mantenible, ya que es sencillo localizar dónde se deben aplicar ajustes o mejoras en la lógica. Además, cada capa conserva su responsabilidad bien definida y evita la duplicidad de código en diferentes controladores.
Todas las lecciones de Node
Accede a todas las lecciones de Node y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Node.js
Introducción Y Entorno
Fundamentos Del Entorno Node.js
Introducción Y Entorno
Módulo Http Y Https
Http Y Api Rest
Http Params, Headers Y Body
Http Y Api Rest
Validación De Datos
Http Y Api Rest
Conexión A Bases De Datos Sin Orm
Persistencia
Creación De Consultas Básicas (Crud) Sin Orm
Persistencia
Módulo Fs
Sistema De Archivos
Introducción A La Seguridad
Seguridad
Sesiones Y Cookies
Seguridad
Roles Y Permisos
Seguridad
Testing En Node.js
Testing
Estructura De Carpetas
Arquitectura
Configuración Y Variables De Entorno
Arquitectura
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender la separación de responsabilidades en arquitectura MVC.
- Implementar controladores que gestionen peticiones en Node.js.
- Utilizar modelos para manipular datos de forma escalable.
- Generar vistas en diversos formatos como HTML y JSON.
- Aplicar servicios o capas de dominio para aislar la lógica de negocio.
- Facilitar la mantenibilidad y escalabilidad del código.
- Promover la separación de lógica de negocio y controladores.