Configuración del proyecto Node.js y Express

Lectura
25 min~8 min lectura

Configuración del Proyecto Node.js y Express

En esta lección aprenderás a configurar un proyecto profesional de Node.js y Express desde cero. Veremos cómo organizar la estructura de directorios, instalar las dependencias necesarias y configurar el entorno de desarrollo para construir APIs RESTful robustas y mantenibles.

CONCEPTO CLAVE: Un proyecto bien estructurado es la base de una API RESTful escalable. La organización del código afecta directamente la mantenibilidad, las pruebas y la colaboración en equipo.

Requisitos Previos

Antes de comenzar, asegúrate de tener instalado lo siguiente en tu sistema:

  • Node.js versión 18.x o superior (incluye npm)
  • Visual Studio Code o cualquier editor de código moderno
  • Postman o Insomnia para probar endpoints
  • Conceptos básicos de JavaScript y programación asíncrona
📌 Nota importante: Verifica tu instalación ejecutando node --version y npm --version en tu terminal. Ambas versiones deben mostrarse sin errores.

Inicialización del Proyecto

  1. Crear el directorio del proyecto: Abre tu terminal y ejecuta mkdir mi-api-rest && cd mi-api-rest
  2. Inicializar package.json: Ejecuta npm init y sigue las instrucciones del asistente interactivo
  3. Instalar Express y dependencias: npm install express cors helmet morgan dotenv
  4. Instalar dependencias de desarrollo: npm install --save-dev nodemon eslint
  5. Crear la estructura de carpetas: Organiza tu código siguiendo las mejores prácticas

Estructura del Proyecto

Una estructura de directorios bien definida es crucial para proyectos de mediana y gran escala. Te recomiendo la siguiente organización:

mi-api-rest/
├── src/
│   ├── config/           # Configuración de la aplicación
│   │   └── index.js
│   ├── controllers/      # Lógica de los endpoints
│   │   └── userController.js
│   ├── middleware/       # Funciones intermedias
│   │   ├── errorHandler.js
│   │   └── validator.js
│   ├── models/           # Definición de esquemas
│   │   └── User.js
│   ├── routes/           # Definición de rutas
│   │   └── userRoutes.js
│   ├── services/        # Lógica de negocio
│   │   └── userService.js
│   ├── utils/            # Funciones auxiliares
│   │   └── helpers.js
│   └── app.js            # Punto de entrada principal
├── .env                  # Variables de entorno
├── .env.example          # Plantilla de variables
├── .gitignore            # Archivos ignorados por Git
├── package.json
└── README.md
💡 Consejo profesional: Mantén tu código modular. Cada carpeta tiene una responsabilidad específica, lo que facilita la búsqueda de errores y la adición de nuevas funcionalidades sin afectar otras partes del sistema.

Configuración del Archivo package.json

Después de ejecutar npm init, tu archivo package.json básico se verá así:

{
  "name": "mi-api-rest",
  "version": "1.0.0",
  "description": "API RESTful con Node.js y Express",
  "main": "src/app.js",
  "scripts": {
    "start": "node src/app.js",
    "dev": "nodemon src/app.js",
    "test": "jest",
    "lint": "eslint src/"
  },
  "keywords": ["api", "rest", "nodejs", "express"],
  "author": "Tu Nombre",
  "license": "MIT"
}
⚠️ Advertencia importante: Nunca subas el archivo .env a tu repositorio Git. Este archivo contiene información sensible como claves de API, contraseñas de base de datos y tokens de acceso. Utiliza siempre .gitignore para excluirlo.

Instalación de Dependencias

Dependencias de Producción

Paquete Propósito Versión recomendada
express Framework web principal ^4.18.2
cors Manejo de Cross-Origin Resource Sharing ^2.8.5
helmet Seguridad mediante headers HTTP ^7.1.0
morgan Logging de peticiones HTTP ^1.10.0
dotenv Variables de entorno ^16.3.1
express-validator Validación de datos de entrada ^7.0.1

Dependencias de Desarrollo

Paquete Propósito
nodemon Reinicio automático del servidor durante desarrollo
eslint Análisis estático de código JavaScript
prettier Formato automático de código

Configuración del Archivo de Entrada

Ahora crearemos el archivo principal src/app.js con todas las configuraciones esenciales:

// src/app.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const errorHandler = require('./middleware/errorHandler');

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

// Middlewares de seguridad
app.use(helmet());

// Middleware CORS
app.use(cors({
  origin: process.env.CORS_ORIGIN || '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

// Logging de peticiones
app.use(morgan('combined'));

// Parsing de JSON
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));

// Rutas de la API
app.get('/api/v1/health', (req, res) => {
  res.status(200).json({
    status: 'success',
    message: 'API funcionando correctamente',
    timestamp: new Date().toISOString()
  });
});

// Manejo de rutas no encontradas
app.use((req, res) => {
  res.status(404).json({
    status: 'error',
    message: `Ruta ${req.originalUrl} no encontrada`
  });
});

// Middleware de manejo de errores
app.use(errorHandler);

// Inicio del servidor
app.listen(PORT, () => {
  console.log(`🚀 Servidor ejecutándose en http://localhost:${PORT}`);
  console.log(`📚 Documentación: http://localhost:${PORT}/api/v1/docs`);
});

module.exports = app;
La modularización no es solo una buena práctica, es una necesidad. Un código bien organizado permite que múltiples desarrolladores trabajen simultáneamente sin conflictos.

Configuración de Variables de Entorno

Crea un archivo .env en la raíz del proyecto:

# Entorno
NODE_ENV=development

# Servidor
PORT=3000

# CORS
CORS_ORIGIN=http://localhost:5173

# Base de datos (para lecciones futuras)
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mi_api_db
DB_USER=postgres
DB_PASSWORD=tu_contraseña_segura

# JWT (para lecciones futuras)
JWT_SECRET=tu_secreto_muy_largo_y_aleatorio
JWT_EXPIRES_IN=7d
💡 Generador de secretos: Usa crypto.randomBytes(64).toString('hex') en Node.js para generar secretos seguros.

Middleware de Manejo de Errores

El manejo de errores es fundamental para cualquier API profesional. Crea src/middleware/errorHandler.js:

// src/middleware/errorHandler.js

class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true;

    Error.captureStackTrace(this, this.constructor);
  }
}

const errorHandler = (err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  // Respuesta de error
  res.status(err.statusCode).json({
    status: err.status,
    message: err.message,
    ...(process.env.NODE_ENV === 'development' && {
      error: err,
      stack: err.stack
    })
  });
};

module.exports = { AppError, errorHandler };
Ver más: ¿Por qué usar clases de error personalizadas?

Las clases de error personalizadas como AppError nos permiten:

  • Diferenciar entre errores operativos (esperados) y errores de programación (bugs)
  • Incluir códigos de estado HTTP de forma consistente
  • Marcar errores como "operacionales" para filtrado en logging
  • Mantener un formato de respuesta uniforme para todos los errores

En producción, los errores operativos deben mostrarse al cliente, mientras que los errores de programación deben registrarse internamente sin exponer detalles sensibles.

Configuración de Scripts npm

Añade scripts útiles a tu package.json:

"scripts": {
  "start": "node src/app.js",
  "dev": "nodemon src/app.js",
  "prod": "NODE_ENV=production node src/app.js",
  "test": "jest --coverage",
  "test:watch": "jest --watch",
  "lint": "eslint src/ --fix",
  "format": "prettier --write \"src/**/*.js\"",
  "prepare": "husky install"
}

Configuración de ESLint

Crea un archivo .eslintrc.json para mantener la consistencia del código:

{
  "env": {
    "node": true,
    "es2021": true,
    "jest": true
  },
  "extends": ["eslint:recommended"],
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "rules": {
    "no-unused-vars": "warn",
    "no-console": "off",
    "consistent-return": "error",
    "prefer-const": "error"
  }
}
📌 Integración con Husky: Para añadir git hooks que ejecuten lint antes de cada commit, instala npm install --save-dev husky lint-staged y configura los hooks apropiadamente.

Prueba de la Configuración

Verifica que todo funcione ejecutando:

npm run dev

Deberías ver en tu terminal:

[nodemon] starting `node src/app.js`
🚀 Servidor ejecutándose en http://localhost:3000
📚 Documentación: http://localhost:3000/api/v1/docs

Ahora, en otra terminal o en tu navegador, prueba el endpoint de salud:

curl http://localhost:3000/api/v1/health

Deberías recibir:

{
  "status": "success",
  "message": "API funcionando correctamente",
  "timestamp": "2024-01-15T10:30:00.000Z"
}
⚠️ Recuerda: Detén el servidor con Ctrl+C antes de continuar. Cada vez que hagas cambios en el código, nodemon reiniciará automáticamente el servidor.

Resumen de lo Aprendido

En esta lección hemos cubierto los fundamentos esenciales para configurar un proyecto profesional de Node.js y Express:

  • ✓ Estructura de directorios escalable y mantenible
  • ✓ Configuración de package.json con scripts útiles
  • ✓ Instalación de middleware de seguridad y utilidad
  • ✓ Configuración de variables de entorno con dotenv
  • ✓ Middleware personalizado para manejo de errores
  • ✓ Configuración de herramientas de desarrollo (ESLint, Prettier)
  • ✓ Verificación de que el servidor funciona correctamente
🧠 Quiz

¿Cuál es el propósito principal del middleware helmet en una API Express?

  • A) Aumentar la velocidad del servidor
  • B) Configurar automáticamente headers HTTP de seguridad
  • C) Validar los datos de entrada del usuario
  • D) Conectar con bases de datos
✅ Respuesta correcta: B) helmet establece automáticamente diversos headers HTTP relacionados con la seguridad para proteger la aplicación contra ataques comunes como XSS, clickjacking, y otras vulnerabilidades web.
Ver más: Mejores prácticas adicionales

Logging Avanzado con Winston

Para aplicaciones en producción, considera reemplazar morgan con winston, que ofrece más flexibilidad:

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

Rate Limiting

Protege tu API contra ataques de denegación de servicio y abuso:

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutos
  max: 100, // límite de 100 peticiones por IP
  message: 'Demasiadas peticiones desde esta IP, intenta más tarde'
});

app.use('/api', limiter);

En la siguiente lección, profundizaremos en la creación de rutas y controladores para tu API RESTful, implementando operaciones CRUD completas con validación de datos.