Inicio / Artículos / Un marco práctico para llevar las aplicaciones Node.js a producción

Un marco práctico para llevar las aplicaciones Node.js a producción

Recorra una lista de verificación completa para la producción de aplicaciones Node.js, que abarca servidores, secretos, gestión de procesos, HTTPS, CI/CD, registro de actividades y copias de seguridad.

2042 palabras

Ejecutar una aplicación Node.js en tu propia máquina es la parte fácil.

npm run dev

Tu API responde. Tu base de datos se conecta. Todo parece funcionar bien.

Luego alguien pregunta:

"Bueno, ¿cómo desplegamos esto en producción?"

Es entonces cuando se hace evidente que construir la API era solo la mitad del trabajo.

Pasar a producción plantea todo un nuevo conjunto de preguntas:

  • ¿Dónde se ejecutará realmente la aplicación?
  • ¿Qué la mantendrá en funcionamiento si se cae?
  • ¿Cómo llega el tráfico entrante al proceso de Node.js?
  • ¿Dónde deben almacenarse las variables de entorno?
  • ¿Cómo se configura HTTPS?
  • ¿Cómo se lanzan nuevas versiones?
  • ¿Cómo se capturan los errores cuando ocurren?
  • ¿Qué sucede si el servidor mismo se reinicia?

Aquí hay un marco práctico para planificar una configuración de producción en Node.js.

1. Comprender la arquitectura de producción

Una configuración básica de producción suele verse así:

                    Internet
                       │
                       ▼
                  ┌─────────┐
                  │  Nginx  │
                  │  :80/443│
                  └────┬────┘
                       │
                       ▼
                ┌─────────────┐
                │   Node.js   │
                │ Application │
                └──────┬──────┘
                       │
              ┌────────┼────────┐
              ▼        ▼        ▼
          PostgreSQL  Redis   External APIs

El principio clave es que los usuarios finales generalmente no deberían conectarse directamente a algo como:

localhost:3000

En su lugar, Nginx se coloca en la parte frontal, aceptando solicitudes públicas y enviándolas a tu aplicación Node.js.

2. Preparar el servidor

Puedes obtener un VPS o una máquina virtual en la nube de proveedores como AWS, GCP, Azure o DigitalOcean.

Antes que nada, necesitas configurar la propia máquina.

En Ubuntu, esto suele comenzar con:

sudo apt update
sudo apt upgrade -y

A partir de ahí, instala las herramientas que necesite tu aplicación.

Para Node.js en sí, generalmente es una buena idea instalarlo a través de un gestor de versiones como nvm, para poder controlar con exactitud qué versión se ejecuta.

Confirme las versiones instaladas con:

node -v
npm -v

Cualquier versión de Node que se ejecute en producción debe coincidir con la que pruebe localmente.

Parece un detalle menor, pero versiones incompatibles pueden causar errores frustrantes y difíciles de rastrear una vez desplegados.

3. No incluya secretos en su código

Este error es fácil de cometer y fácil de evitar.

Evite codificar credenciales de esta manera:

const DATABASE_URL =
  "postgresql://user:password@database.com/mydb";

Y nunca incluya secretos en el historial de Git.

En su lugar, defínalos como variables de entorno:

NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://...
REDIS_URL=redis://...
JWT_SECRET=...

Luego léalos en su código utilizando:

process.env.DATABASE_URL

Asegúrese de que sus archivos .env estén excluidos del control de versiones:

.env
.env.production

Una sola contraseña de base de datos expuesta puede causar mucho más daño que cualquier error en el despliegue.

4. Construir la aplicación

Antes de lanzar la app, instale solo las dependencias para producción y ejecute un paso de compilación si su framework lo requiere.

Un proyecto en TypeScript, por ejemplo, podría ejecutarse de la siguiente manera:

npm ci
npm run build

Y luego iniciar el resultado compilado con:

npm start

Los comandos exactos variarán según su stack tecnológico.

Lo importante es este principio:

El tráfico de producción debe dirigirse a la versión compilada para producción, nunca al servidor de desarrollo.

En otras palabras, no deje accidentalmente algo como:

npm run dev

funcionando como su proceso en producción.

5. ¿Qué sucede si Node.js falla?

Supongamos que lanza su app directamente de esta manera:

node dist/server.js

En algún momento, algo sale mal y el proceso termina inesperadamente.

Su API está ahora fuera de línea, sin nada que pueda restaurarla.

Este es exactamente el problema que resuelve un gestor de procesos.

PM2 es una opción ampliamente utilizada para esto.

Instálelo de forma global:

npm install -g pm2

Luego ejecute su aplicación bajo la supervisión de PM2:

pm2 start dist/server.js --name my-api

Verifique su estado:

pm2 status

Mire los registros:

pm2 logs my-api

O reinícielo cuando sea necesario:

pm2 restart my-api

El punto no es solo la facilidad de uso. Se trata de que su proceso esté ahora bajo supervisión activa, en lugar de ejecutarse en una ventana de terminal con la esperanza de que nada lo detenga.

6. Hacer que la aplicación se inicie después de un reinicio del servidor

Los servidores sí se reinician, ya sea intencionadamente o no.

Por ejemplo:

Server reboot
     ↓
Operating system starts
     ↓
Node.js application?

No querrá tener que conectarse vía SSH manualmente cada vez que esto ocurre.

PM2 puede generar un script de inicio para su sistema:

pm2 startup

Luego, guarde la lista de procesos que se están ejecutando actualmente:

pm2 save

Con esto en su lugar, su aplicación podrá volver a estar activa automáticamente después de un reinicio.

7. Colocar Nginx delante de Node.js

Supongamos que su servidor Node.js está vinculado a:

localhost:3000

Pero sus usuarios están accediendo desde:

https://api.example.com

Nginx puede cerrar esa brecha al actuar como un proxy inverso.

Una configuración simplificada podría verse así:

server {
    listen 80;server_name api.example.com;
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Con esto, la ruta de la solicitud se convierte en:

User
  ↓
https://api.example.com
  ↓
Nginx :80
  ↓
Node.js :3000

De esta manera, el puerto en el que escucha su aplicación Node.js nunca necesita exponerse directamente a Internet.

8. Agregar HTTPS

Ejecutar su API de producción en modo plano

http://

No es lo suficientemente bueno. Necesitas que se sirva a través de

https://

Un enfoque ampliamente utilizado es combinar Let's Encrypt con Certbot.

Comienza instalando Certbot:

sudo apt install certbot python3-certbot-nginx

Luego solicita y aplica un certificado para tu dominio:

sudo certbot --nginx -d api.example.com

Certbot se encarga de proporcionar el certificado y configurar HTTPS en tu nombre.

Una vez hecho esto, el flujo de solicitud se ve así:

Client
   ↓
HTTPS
   ↓
Nginx
   ↓
Node.js
   ↓
Database / Redis

9. No olvides tu firewall

No todas las puertas de tu servidor necesitan ser accesibles desde el mundo exterior.

Típicamente, querrás que el público pueda acceder a:

80   → HTTP
443  → HTTPS

junto con el acceso SSH en:

22

Por otro lado, las puertas utilizadas por PostgreSQL o Redis generalmente no deben estar abiertas a Internet a menos que tengas una razón específica y controles de acceso sólidos en vigor.

Las reglas exactas variarán según su configuración, pero la regla fundamental permanece igual:

Solo exponga lo que realmente necesite ser expuesto.

10. El despliegue manual funciona, hasta que deja de hacerlo

Al principio, un despliegue podría ser simplemente:

git pull
npm install
npm run build
pm2 restart my-api

Eso está perfectamente bien para un proyecto pequeño.

Pero tarde o temprano notará que está repitiendo una y otra vez la misma secuencia:

Developer pushes code
       ↓
SSH into server
       ↓
git pull
       ↓
install dependencies
       ↓
build
       ↓
restart

En ese punto, vale la pena automatizarlo.

11. CI/CD cambia el flujo de trabajo

Al utilizar herramientas como GitHub Actions, el proceso puede pasar a ser:

Developer
    ↓
git push
    ↓
GitHub
    ↓
CI/CD Pipeline
    ↓
Build + Test
    ↓
Deploy
    ↓
Production

Una definición mínima de flujo de trabajo podría verse así:

name: Deploy
on:
  push:
    branches:
      - mainjobs:
  deploy:
    runs-on: ubuntu-latest    steps:
      - uses: actions/checkout@v4      - name: Install dependencies
        run: npm ci      - name: Build
        run: npm run build      - name: Test
        run: npm test

Los pasos específicos de despliegue dependerán de su propia infraestructura.

Lo que es más importante es el principio subyacente:

No automatice un proceso de despliegue que aún no comprende.

Aprenda primero cómo hacerlo a mano.

Solo entonces convierta los pasos repetitivos en automatización.

12. El registro de logs no es opcional

Una aplicación puede parecer estar funcionando bien incluso cuando los usuarios reales se encuentran con errores.

Los logs son la forma de averiguarlo.

Como mínimo, necesita respuestas a:

When did the error happen?
Which endpoint failed?
What status code was returned?
What was the error?
How long did the request take?

Un ejemplo básico:

console.error({
  message: error.message,
  endpoint: req.originalUrl,
  method: req.method,
  timestamp: new Date().toISOString()
});

Para cualquier sistema que funcione a escala real, los logs estructurados combinados con una agregación centralizada de logs le servirán mucho más que dispersar llamadas a console.log() por todo su código.

13. Monitoree más que solo los errores

La ausencia de excepciones no significa que su sistema esté sano.

También necesita tener visibilidad de aspectos como:

CPU
Memory
Disk
Request latency
Error rate
Database performance
Redis health
Traffic

Por ejemplo:

Requests       → 1,500/min
Average latency → 180ms
Error rate      → 0.4%
CPU             → 42%
Memory          → 61%

Métricas como estas le brindan una imagen mucho más completa de cómo está funcionando realmente su sistema.

14. Las copias de seguridad son más importantes que el despliegue

Aquí hay una situación en la que la mayoría de los desarrolladores preferirían no pensar:

Production database
       ↓
Something goes wrong
       ↓
Data disappears

Siempre puede volver a desplegar el código de su aplicación.

No obstante, su base de datos puede contener datos que simplemente no se pueden regenerar una vez que se pierden.

Por eso las copias de seguridad tampoco son opcionales.

Necesita un plan de respaldo para todo lo importante en producción, y, igualmente importante, debe saber realmente cómo restaurar desde esas copias de seguridad.

Una copia de seguridad que nunca ha probado a restaurar no es una en la que deba confiar.

15. Los despliegues sin interrupciones son un problema aparte

En algún momento, dejar su aplicación fuera de línea brevemente para realizar un despliegue deja de ser aceptable.

Considere este escenario:

Old version running
       ↓
New version deployed
       ↓
Traffic gradually moves
       ↓
Old version removed

Dependiendo de cómo esté configurada su infraestructura, podría recurrir a:

  • Ejecutar varias copias de su proceso Node.js en paralelo
  • El modo de clúster integrado de PM2
  • Un balanceador de carga que distribuye el tráfico entre las instancias
  • Desplegar actualizaciones de forma gradual, nodo por nodo, en lugar de todas a la vez
  • Cambiar el tráfico entre un entorno antiguo y uno nuevo (estilo azul-verde)
  • Empaquetar la aplicación en contenedores
  • Orquestar todo con Kubernetes

Aun así, tener una API de Node.js no implica automáticamente que necesite Kubernetes.

Manténgalo simple al principio, y desarrolle la arquitectura solo cuando sus requisitos reales lo exijan.

16. Mi lista de verificación para producción

Antes de considerar que una aplicación Node.js está lista para producción, esto es lo que vale la pena revisar:

[ ] Production environment configured
[ ] Secrets stored securely
[ ] Database connection configured
[ ] Redis configured if required
[ ] Production build tested
[ ] Process manager configured
[ ] Application restart tested
[ ] Nginx configured
[ ] HTTPS configured
[ ] Firewall configured
[ ] Logs available
[ ] Error monitoring configured
[ ] Database backups configured
[ ] Backup restoration tested
[ ] Deployment process documented
[ ] CI/CD configured if needed
[ ] Health check endpoint available

17. La arquitectura que tengo en mente

Una configuración básica para producción de un sistema Node.js suele converger en algo similar a esto:

                    ┌─────────────┐
                    │   Internet  │
                    └──────┬──────┘
                           │
                           ▼
                    ┌─────────────┐
                    │    Nginx    │
                    │  SSL / Proxy│
                    └──────┬──────┘
                           │
                 ┌─────────┴─────────┐
                 ▼                   ▼
          ┌────────────┐      ┌────────────┐
          │  Node.js   │      │  Node.js   │
          │ Instance 1 │      │ Instance 2 │
          └─────┬──────┘      └─────┬──────┘
                │                   │
                └─────────┬─────────┘
                          │
              ┌───────────┼───────────┐
              ▼           ▼           ▼
         PostgreSQL     Redis    External APIs

Y alrededor de ese núcleo, normalmente se desea tener:

Monitoring
Logging
Backups
CI/CD
Security

Reflexión final

Lanzar una aplicación Node.js a producción nunca es simplemente:

npm start

El trabajo real en producción implica planificar todo lo que ocurre después de que el proceso arranque.

¿Cuál es su plan si ocurre un fallo inesperado?

¿Cómo se comporta el sistema una vez que se reinicia el servidor?

¿Cuál es la respuesta cuando el tráfico entrante aumenta repentinamente?

¿Qué sucede en el momento en que la base de datos se vuelve inaccesible?

¿Cómo se recupera una vez que se lanza una versión defectuosa?

¿Cuál es el protocolo a seguir si una credencial queda expuesta por error?

Ese es el vacío entre:

“Funciona en mi máquina.”

y

“Funciona de manera fiable en producción.”

No necesitas una infraestructura enorme desde el primer día.

Comienza con algo sencillo.

Entiende cada componente que añadas.

Automatiza todo lo que se repite.

Observa las métricas que realmente importan.

Solo introduce complejidad cuando el sistema lo requiera de verdad.

El despliegue no es la meta final del desarrollo. Es donde realmente comienza el funcionamiento de tu software.

Lecturas relacionadas

  • Construyendo un manejo de errores de nivel producción en aplicaciones Node.js — Aprenda cómo clasificar los errores en Node.js, diseñar una jerarquía de errores personalizada, centralizar el manejo asincrónico de errores y proteger las trazas de pila para garantizar la resiliencia en entornos de producción.