API Node.js en Docker con datos en PostgreSQL

Índice de contenido
  1. Prepara tabla y proyecto
  2. Implementa src/index.js
  3. Construye la imagen
  4. Comprueba comportamiento
  5. Errores de conexión y contrato HTTP
  6. Preguntas frecuentes
    1. ¿Los datos sobreviven al reinicio de la API?
    2. ¿Las consultas parametrizadas sustituyen toda validación?
    3. ¿Por qué la API no conecta a localhost?
  7. En pocas palabras
  8. Conclusión
  9. Fuentes y documentación

Esta API de práctica guarda notas en PostgreSQL y permite crearlas y consultarlas. El ejemplo usa Node.js, Express y el cliente pg, con consultas parametrizadas. No presenta un arreglo en memoria como si fuera almacenamiento persistente.

Necesitas una base PostgreSQL accesible desde el contenedor y una cuenta limitada a sus datos. Prepárala por un procedimiento autorizado y conserva sus credenciales fuera del repositorio.

Prepara tabla y proyecto

En la base del laboratorio, crea una tabla con el usuario propietario apropiado:

CREATE TABLE notas (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  texto text NOT NULL CHECK (length(texto) <= 500)
);

En una carpeta nueva:

npm init -y
npm install express pg
mkdir src

Conserva package.json y package-lock.json. Revisa las versiones de dependencias y utiliza una rama Node con soporte.

Implementa src/index.js

const express = require('express');
const { Pool } = require('pg');
const pool = new Pool();
const app = express();
app.use(express.json({ limit: '16kb' }));

app.get('/health', async (req, res, next) => {
  try {
    await pool.query('SELECT 1');
    res.json({ status: 'ok' });
  } catch (error) { next(error); }
});
app.get('/notas', async (req, res, next) => {
  try {
    const result = await pool.query(
      'SELECT id, texto FROM notas ORDER BY id DESC LIMIT 100'
    );
    res.json(result.rows);
  } catch (error) { next(error); }
});
app.post('/notas', async (req, res, next) => {
  const texto = req.body?.texto;
  if (typeof texto !== 'string' || !texto.trim() || texto.length > 500) {
    return res.status(400).json({ error: 'Texto no válido' });
  }
  try {
    const result = await pool.query(
      'INSERT INTO notas(texto) VALUES($1) RETURNING id, texto',
      [texto.trim()]
    );
    res.status(201).json(result.rows[0]);
  } catch (error) { next(error); }
});
app.use((error, req, res, next) => {
  if (error.type === 'entity.parse.failed') {
    return res.status(400).json({ error: 'JSON no válido' });
  }
  if (error.type === 'entity.too.large') {
    return res.status(413).json({ error: 'Solicitud demasiado grande' });
  }
  console.error(error.message);
  res.status(500).json({ error: 'No se pudo completar la operación' });
});
app.listen(3000, '0.0.0.0');

Pool utiliza las variables de conexión reconocidas por pg, como PGHOST, PGDATABASE, PGUSER y PGPASSWORD. Define valores correctos para la red del contenedor. En producción, configura también TLS y políticas del cliente según el servidor.

Construye la imagen

Guarda Dockerfile:

FROM node:24-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --chown=node:node src ./src
USER node
EXPOSE 3000
CMD ["node", "src/index.js"]

Excluye node_modules, .git, .env y archivos privados con .dockerignore. No copies credenciales a la imagen: seguirían presentes en sus capas aunque después las borraras.

docker build -t api-notas:lab .

Al ejecutar, proporciona configuración por el mecanismo privado de tu entorno y la red que alcance PostgreSQL. Publica 3000 en 127.0.0.1 para el laboratorio. localhost dentro de la API no apunta automáticamente al contenedor de la base.

Comprueba comportamiento

Verifica /health y realiza un POST a /notas con un JSON de texto. Comprueba respuesta 201, lectura posterior y rechazo 400 para entradas inválidas. Reinicia únicamente la API y confirma que la nota sigue en PostgreSQL.

Este servicio no tiene autenticación y no debe publicarse como una aplicación terminada. Necesita diseño de acceso, pruebas más amplias, cierre ordenado, observabilidad y política de errores.

Errores de conexión y contrato HTTP

Si falla /health, comprueba primero la conexión desde la misma red de la API: nombre, puerto, base, usuario y política TLS. No otorgues acceso administrativo a la cuenta para ocultar un error de configuración.

Para comprobar el contrato, prueba también un cuerpo sin texto, JSON mal formado y una solicitud demasiado grande. Una respuesta controlada permite distinguir un dato inválido de una indisponibilidad del servidor. Registra el fallo de manera privada y evita devolver credenciales o consultas completas al cliente.

Preguntas frecuentes

¿Los datos sobreviven al reinicio de la API?

Sí si se guardaron correctamente en PostgreSQL y la base conserva su almacenamiento. Debes comprobar también la persistencia y respaldo de la propia base.

¿Las consultas parametrizadas sustituyen toda validación?

No. Ayudan a separar valores de SQL, pero necesitas validar tipo, tamaño, reglas del negocio y permisos.

¿Por qué la API no conecta a localhost?

Dentro del contenedor, localhost se refiere a ese mismo contenedor. Utiliza el host o nombre del servicio correspondiente a la red real.

En pocas palabras

Una API que promete persistencia debe escribir realmente en una base, no solo en un arreglo de memoria. La conexión, los permisos y las comprobaciones son parte del ejemplo.

Conclusión

Prueba altas, lecturas y errores y confirma persistencia tras reiniciar la API. Antes de exponerla, añade autenticación, límites, TLS y mantenimiento conforme a su uso.

Para continuar, revisa Docker: qué es y cómo usarlo para ejecutar contenedores y Cómo instalar Docker en Ubuntu 24.04: guía práctica paso a paso.

Fuentes y documentación

Documentación de expressjs.com.

Documentación de docs.docker.com.

Documentación de nodejs.org.

Subir

Descubre más de Sergio Caballero

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Continuar leyendo