Compartir comentarios
Las respuestas se generan en base a la documentación.

Preinicialización de bases de datos con esquema y datos al inicio para entornos de desarrollo

La preinicialización (pre-seeding) de bases de datos con datos y esquemas esenciales durante el desarrollo local es una práctica común para mejorar el flujo de trabajo de desarrollo y pruebas. Al simular escenarios del mundo real, esta práctica ayuda a detectar problemas del frontend a tiempo, garantiza la alineación entre los administradores de bases de datos y los ingenieros de software, y facilita una colaboración más fluida. La preinicialización ofrece beneficios como despliegues seguros, coherencia entre entornos y detección temprana de problemas, mejorando en última instancia el proceso de desarrollo general.

En esta guía, aprenderás a:

  • Usar Docker para iniciar un contenedor de Postgres
  • Preinicializar Postgres usando un script SQL
  • Preinicializar Postgres copiando archivos SQL en la imagen de Docker
  • Preinicializar Postgres usando código JavaScript

Uso de Postgres con Docker

La imagen oficial de Docker para Postgres proporciona una forma conveniente de ejecutar la base de datos Postgres en tu máquina de desarrollo. Una imagen Docker de Postgres es un entorno preconfigurado que encapsula el sistema de bases de datos PostgreSQL. Es una unidad autónoma lista para ejecutarse en un contenedor Docker. Al usar esta imagen, puedes configurar una instancia de Postgres de manera rápida y directa, sin necesidad de realizar configuraciones manuales.

Requisitos previos

Se requieren los siguientes requisitos previos para seguir esta guía práctica:

Iniciar Postgres

Inicia una demostración rápida de Postgres siguiendo estos pasos:

  1. Abre la terminal y ejecuta el siguiente comando para iniciar un contenedor de Postgres.

    Este ejemplo iniciará un contenedor de Postgres, expondrá el puerto 5432 en el host para permitir que una aplicación que se ejecuta de forma nativa se conecte a él con la contraseña mysecretpassword.

    $ docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=mysecretpassword postgres
    
  2. Verifica que Postgres esté en funcionamiento seleccionando el contenedor y revisando los registros en el panel de Docker Desktop.

    PostgreSQL Database directory appears to contain a database; Skipping initialization
    
    2024-09-08 09:09:47.136 UTC [1] LOG:  starting PostgreSQL 16.4 (Debian 16.4-1.pgdg120+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
    2024-09-08 09:09:47.137 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
    2024-09-08 09:09:47.137 UTC [1] LOG:  listening on IPv6 address "::", port 5432
    2024-09-08 09:09:47.139 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
    2024-09-08 09:09:47.142 UTC [29] LOG:  database system was shut down at 2024-09-08 09:07:09 UTC
    2024-09-08 09:09:47.148 UTC [1] LOG:  database system is ready to accept connections
  3. Conéctate a Postgres desde el sistema local.

    psql es la consola interactiva de PostgreSQL que se utiliza para conectarse a una base de datos Postgres y te permite comenzar a ejecutar comandos SQL. Asumiendo que ya tienes instalada la utilidad psql en tu sistema local, es hora de conectarse a la base de datos. Ejecuta el siguiente comando en tu terminal local:

    $ docker exec -it postgres psql -h localhost -U postgres
    

    Ahora puedes ejecutar cualquier consulta o comando SQL que necesites dentro del prompt de psql.

    Usa \q o \quit para salir de la consola interactiva de Postgres.

Preinicializar la base de datos Postgres usando un script SQL

Una vez que te hayas familiarizado con Postgres, es hora de ver cómo preinicializarlo con datos de ejemplo. En esta demostración, primero crearás un script con comandos SQL. El script define la base de datos y la estructura de la tabla, e inserta datos de ejemplo. Luego te conectarás a la base de datos para verificar los datos.

Asumiendo que tienes una instancia de base de datos Postgres en funcionamiento, sigue estos pasos para inicializar la base de datos.

  1. Crea un archivo vacío llamado seed.sql y agrega el siguiente contenido.

    CREATE DATABASE sampledb;
    
    \c sampledb
    
    CREATE TABLE users (
      id SERIAL PRIMARY KEY,
      name VARCHAR(50),
      email VARCHAR(100) UNIQUE
    );
    
    INSERT INTO users (name, email) VALUES
      ('Alpha', '[email protected]'),
      ('Beta', '[email protected]'),
      ('Gamma', '[email protected]');  

    El script SQL crea una nueva base de datos llamada sampledb, se conecta a ella y crea una tabla users. La tabla incluye un id autoincremental como clave primaria, un campo name con una longitud máxima de 50 caracteres y un campo email único de hasta 100 caracteres.

    Después de crear la tabla, el comando INSERT introduce tres usuarios en la tabla users con sus respectivos nombres y correos electrónicos. Esta configuración establece una estructura de base de datos básica para almacenar información de usuarios con direcciones de correo electrónico únicas.

  2. Inicializar la base de datos.

    Es hora de enviar el contenido de seed.sql directamente a la base de datos. El comando se utiliza para ejecutar el script SQL llamado seed.sql en la base de datos Postgres llamada sampledb.

    $ cat seed.sql | docker exec -i postgres psql -h localhost -U postgres -f-
    

    Una vez que la consulta se haya ejecutado, verás los siguientes resultados:

    CREATE DATABASE
    You are now connected to database "sampledb" as user "postgres".
    CREATE TABLE
    INSERT 0 3
  3. Ejecuta el siguiente comando psql para verificar si la tabla llamada users se ha poblado en la base de datos sampledb o no.

    $ docker exec -it postgres psql -h localhost -U postgres sampledb
    

    Puedes ejecutar \l en la consola de psql para listar todas las bases de datos en el servidor de Postgres.

    sampledb=# \l
                                                List of databases
    Name    |  Owner   | Encoding |  Collate   |   Ctype    | ICU Locale | Locale Provider |   Access privileges
    -----------+----------+----------+------------+------------+------------+-----------------+-----------------------
    postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            |
    sampledb  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            |
    template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | =c/postgres          +
              |          |          |            |            |            |                 | postgres=CTc/postgres
    template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | =c/postgres          +
              |          |          |            |            |            |                 | postgres=CTc/postgres
    (4 rows)
    

    Para recuperar todos los datos de la tabla users, ingresa la siguiente consulta:

    sampledb=# SELECT * FROM users;
    id | name  |       email
    ----+-------+-------------------
     1 | Alpha | [email protected]
     2 | Beta  | [email protected]
     3 | Gamma | [email protected]
    (3 rows)
    

    Usa \q o \quit para salir de la consola interactiva de Postgres.

Preinicializar la base de datos montando un script SQL

En Docker, el montaje (mounting) se refiere a hacer que los archivos o directorios del sistema host sean accesibles dentro de un contenedor. Esto te ayuda a compartir datos o archivos de configuración entre el host y el contenedor, permitiendo una mayor flexibilidad y persistencia.

Una vez que has aprendido a iniciar Postgres y a preinicializar la base de datos usando un script SQL, es hora de aprender a montar un archivo SQL directamente en el directorio de inicialización de los contenedores de Postgres (/docker-entrypoint-initdb.d). El directorio /docker-entrypoint-initdb.d es una carpeta especial en los contenedores Docker de PostgreSQL que se utiliza para inicializar la base de datos cuando el contenedor se inicia por primera vez.

Asegúrate de detener cualquier contenedor de Postgres que esté en ejecución (junto con sus volúmenes) para evitar conflictos de puertos antes de seguir los pasos:

$ docker container stop postgres
  1. Modifica seed.sql con las siguientes entradas:

    CREATE TABLE IF NOT EXISTS users (
     id SERIAL PRIMARY KEY,
     name VARCHAR(50),
     email VARCHAR(100) UNIQUE
    );
    
    INSERT INTO users (name, email) VALUES
     ('Alpha', '[email protected]'),
     ('Beta', '[email protected]'),
     ('Gamma', '[email protected]')
    ON CONFLICT (email) DO NOTHING;
  2. Crea un archivo de texto llamado Dockerfile y copia el siguiente contenido.

    # syntax=docker/dockerfile:1
    FROM postgres:18
    COPY seed.sql /docker-entrypoint-initdb.d/

    Este Dockerfile copia el script seed.sql directamente en el directorio de inicialización del contenedor de PostgreSQL.

  3. Usar Docker Compose.

    El uso de Docker Compose facilita aún más la administración y el despliegue del contenedor de PostgreSQL con la base de datos inicializada. Este archivo compose.yml define un servicio de Postgres llamado db que utiliza la última imagen de Postgres, el cual configura una base de datos con el nombre sampledb, junto con un usuario postgres y la contraseña mysecretpassword.

    services:
      db:
        build:
          context: .
          dockerfile: Dockerfile
        container_name: my_postgres_db
        environment:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: mysecretpassword
          POSTGRES_DB: sampledb
        ports:
          - "5432:5432"
        volumes:
          - data_sql:/var/lib/postgresql   # Almacenamiento persistente de datos
    
    volumes:
      data_sql:

    Mapea el puerto 5432 del host al 5432 del contenedor, lo que te permite acceder a la base de datos Postgres desde fuera del contenedor. También define data_sql para conservar los datos de la base de datos de manera persistente, asegurando que la información no se pierda al detener el contenedor.

    Cabe destacar que el mapeo de puertos al host solo es necesario si deseas conectarte a la base de datos desde programas no contenedorizados. Si contenedorizas el servicio que se conecta a la base de datos, deberías conectarte a ella a través de una red puente (bridge network) personalizada.

  4. Iniciar el servicio de Compose.

    Asumiendo que has colocado el archivo seed.sql en el mismo directorio que el Dockerfile, ejecuta el siguiente comando:

    $ docker compose up -d --build
    
  5. Es hora de verificar si la tabla users se ha poblado con los datos.

    $ docker exec -it my_postgres_db psql -h localhost -U postgres sampledb
    
    sampledb=# SELECT * FROM users;
      id | name  |       email
    ----+-------+-------------------
       1 | Alpha | alpha@example.com
       2 | Beta  | beta@example.com
       3 | Gamma | gamma@example.com
     (3 rows)
    
    sampledb=#

Preinicializar la base de datos usando código JavaScript

Una vez que has aprendido a inicializar la base de datos utilizando varios métodos como scripts SQL, montaje de volúmenes, etc., es hora de intentar lograrlo utilizando código JavaScript.

  1. Crea un archivo .env con lo siguiente:

    POSTGRES_USER=postgres
    POSTGRES_DB_HOST=localhost
    POSTGRES_DB=sampledb
    POSTGRES_PASSWORD=mysecretpassword
    POSTGRES_PORT=5432
  2. Crea un nuevo archivo JavaScript llamado seed.js con el siguiente contenido:

    El siguiente código JavaScript importa el paquete dotenv, el cual se utiliza para cargar variables de entorno desde un archivo .env. El método .config() lee el archivo .env y establece las variables de entorno como propiedades del objeto process.env. Esto te permite almacenar de forma segura información confidencial, como las credenciales de la base de datos, fuera de tu código.

    Luego, crea una nueva instancia de Pool de la biblioteca pg, la cual proporciona un grupo de conexiones (connection pool) para interacciones eficientes con la base de datos. La función seedData está definida para realizar las operaciones de inicialización de la base de datos. Se llama al final del script para iniciar el proceso de inicialización. El bloque try...catch...finally se utiliza para el manejo de errores.

    require('dotenv').config();  // Carga variables de entorno desde el archivo .env
    const { Pool } = require('pg');
    
    // Crea una nueva pool usando variables de entorno
    const pool = new Pool({
      user: process.env.POSTGRES_USER,
      host: process.env.POSTGRES_DB_HOST,
      database: process.env.POSTGRES_DB,
      port: process.env.POSTGRES_PORT,
      password: process.env.POSTGRES_PASSWORD,
    });
    
    const seedData = async () => {
      try {
         // Elimina la tabla si ya existe (opcional)
         await pool.query(`DROP TABLE IF EXISTS todos;`);
    
         // Crea la tabla con la estructura correcta
         await pool.query(`
           CREATE TABLE todos (
             id SERIAL PRIMARY KEY,
             task VARCHAR(255) NOT NULL,
             completed BOOLEAN DEFAULT false
               );
         `   );
    
         // Inserta datos de inicialización
         await pool.query(`
           INSERT INTO todos (task, completed) VALUES
           ('Watch netflix', false),
           ('Finish podcast', false),
           ('Pick up kid', false);
           `);
           console.log('Database seeded successfully!');
         } catch (err) {
           console.error('Error seeding the database', err);
         } finally {
           pool.end();
        }
      };
    
      // Llama a la función seedData para ejecutar el script
      seedData();
  3. Inicia el proceso de inicialización

    $ node seed.js
    

    Deberías ver el siguiente mensaje:

    Database seeded successfully!
  4. Verifica si la base de datos se ha inicializado correctamente:

    $ docker exec -it postgres psql -h localhost -U postgres sampledb
    
    sampledb=# SELECT * FROM todos;
    id |      task      | completed
    ----+----------------+-----------
    1 | Watch netflix  | f
    2 | Finish podcast | f
    3 | Pick up kid    | f
    (3 rows)  
    

Resumen

La preinicialización de una base de datos con esquema y datos al inicio es esencial para crear un entorno de pruebas coherente y realista, lo que ayuda a identificar problemas al principio del desarrollo y a alinear el trabajo del frontend y el backend. Esta guía te ha proporcionado los conocimientos y los pasos prácticos para lograr la preinicialización mediante varios métodos, incluyendo scripts SQL, integración con Docker y código JavaScript.