Introducción

1

Manejo de Bases de Datos Relacionales con Node.js y Postgres

2

Persistencia de Datos con Node.js y PostgreSQL usando Docker

Base de datos

3

Instalación de Docker en Windows, macOS y Ubuntu

4

Configuración de Postgres en Docker con Docker Compose

5

Conexión a Bases de Datos en Contenedores con Docker y PgAdmin

6

Conexión de Node.js a PostgreSQL usando Node PostgreSQL Driver

7

Manejo de conexiones con pool en Node.js y Postgres

8

Manejo Seguro de Variables de Entorno en Node.js

Sequelize

9

Introducción a ORMs con Sequelize y Node.js

10

Creación y Gestión de Modelos con ORM en Node.js

11

CRUD con ORM: Crear, Leer, Actualizar y Eliminar Datos

12

Conexión de ORM a MySQL usando Docker y phpMyAdmin

Migraciones

13

Migraciones de Bases de Datos con SQLite: Configuración Inicial

14

Migraciones Manuales en SQLite: Creación y Ejecución

15

Migraciones en SQL: Modificar Tablas y Agregar Campos

Relaciones

16

Relaciones uno a uno en bases de datos relacionales

17

Relaciones Uno a Uno y Migraciones en Bases de Datos SQL

18

Relaciones Uno a Muchos en Bases de Datos con SQLite

19

Relaciones Uno a Muchos en API Rest con SQLite

Consultas

20

Relaciones Muchos a Muchos en SQL: Órdenes y Productos

21

Relaciones Muchos a Muchos en SQLite con Tablas Ternarias

22

Gestión de Relaciones Muchos a Muchos en Base de Datos

23

Paginación de Datos con SQL: Uso de Limit y Offset

24

Filtros Avanzados en Consultas SQL con SQLite

Despliegue

25

Deployment de Postgres en Heroku: Configuración y Migraciones

26

Buenas prácticas para migraciones en producción

Próximos pasos

27

Migración y CRUD en Bases de Datos con Postgres y Heroku

No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Manejo Seguro de Variables de Entorno en Node.js

8/27
Recursos

¿Cómo mejorar la seguridad al manejar conexiones a bases de datos?

Una conexión asegurada a tu base de datos implica proteger cualquier información sensible que se pueda filtrar desde el código. Las credenciales del usuario y la contraseña de la base de datos no deberían estar nunca directamente en el archivo fuente. En este artículo, exploraremos cómo manejar de manera segura la configuración necesaria mediante variables de entorno. Este método no solo mejora la seguridad, sino que también optimiza tu código para ser más adaptable en diferentes entornos de desarrollo y producción.

¿Qué son las variables de entorno y por qué son importantes?

Las variables de entorno son parámetros externos que afectan cómo se ejecuta un programa. Al manejar credenciales y otros datos sensibles mediante variables de entorno, evitamos que se expongan en nuestro código y, potencialmente, en nuestro repositorio de control de versiones.

Beneficios de usar variables de entorno:

  • Mayor seguridad: Mantienes datos sensibles fuera de los archivos de código.
  • Flexibilidad: Fácil de cambiar entre configuraciones de desarrollo, pruebas y producción.
  • Mantenibilidad: Facilita las actualizaciones y el mantenimiento del código.

¿Cómo configurar un archivo config.js para manejar variables de entorno?

Primero, crea una carpeta llamada config y dentro de ella, un archivo config.js. En este archivo se configurará el acceso a las variables de entorno utilizando el módulo process, que es global en Node.js. Aquí te mostramos un ejemplo básico:

const config = {
  env: process.env.NODE_ENV || 'development',
  port: process.env.PORT || 3000,
  dbUser: process.env.DB_USER,
  dbPassword: process.env.DB_PASSWORD,
  dbHost: process.env.DB_HOST,
  dbName: process.env.DB_NAME,
  dbPort: process.env.DB_PORT || 5432
};

module.exports = { config };

¿Cómo proteger la información de conexión a la base de datos?

En lugar de pasar cada variable individualmente, una práctica recomendada es codificar credenciales sensibles y consolidarlas en una cadena de conexión completa. Así, además de proteger la información, se facilita su uso en diferentes ambientes.

const user = encodeURIComponent(config.dbUser);
const password = encodeURIComponent(config.dbPassword);
const URI = `postgres://${user}:${password}@${config.dbHost}:${config.dbPort}/${config.dbName}`;

const pool = new Pool({
  connectionString: URI
});

¿Cómo usar archivos .env para manejar configuraciones locales?

Crear un archivo .env en tu directorio local te permite manejar las configuraciones necesarias sin exponerlas en el repositorio. Git suele ignorar estos archivos por defecto, por lo que no se vuelven públicos.

Ejemplo de un archivo .env:

NODE_ENV=development
PORT=3000
DB_USER=myDatabaseUser
DB_PASSWORD=myDatabasePassword
DB_HOST=localhost
DB_NAME=myDatabaseName
DB_PORT=5432

Nota importante: Nunca subas tu archivo .env al repositorio de control de versiones. Es buena práctica crear un .env.example para mostrar a los demás desarrolladores qué variables son necesarias, pero sin los valores reales.

¿Cómo integrar el paquete dotenv para manejar las configuraciones?

Instala el paquete dotenv para cargar automáticamente las variables del archivo .env al proceso de Node:

npm install dotenv

Luego configura dotenv en tu aplicación:

require('dotenv').config();

Esta línea carga todas las variables del archivo .env al proceso de Node al inicio de tu aplicación.

¿Qué hacer si algo sale mal con la conexión?

Si experimentas problemas al establecer la conexión, revisa que todos los valores y nombres de las variables de ambiente sean correctos. Un error común podría ser escribir mal una variable en el código o el archivo .env. Asegúrate de que los valores sean correctos y de que el archivo .env esté correctamente configurado.

Mantener tus datos sensibles a salvo y tu aplicación bien configurada son aspectos clave para el desarrollo profesional. Al manejar correctamente las variables de entorno, estarás protegiendo la integridad de tu aplicación y preparándola para un entorno más profesional. Sigue avanzado en tu camino de aprendizaje y práctica las mejores prácticas en cada paso que des. ¡Tú puedes lograrlo!

Aportes 28

Preguntas 20

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Unit Test

💡Es el proceso de ejecutar un programa con la meta de encontrar errores. Si la “prueba” es exitosa, entonces podemos asegurar que, alguna característica o funcionalidad, fue cubierta.

.
Para este caso, sugiero que implementen sus pruebas ya sea de unit test (función o módulo) y/o end-to-end (integración).
.

Configuración

ℹ️Repositorio: link
.
Para adicionar pruebas a nuestro código utilizamos:

  • jest. Engine de pruebas para JavaScript
  • supertes. Handler Process para peticiones HTTP.

.
✨Como retornamos un arreglo como data para validar el cambio de pool podemos definir el siguiente bloque:

import supertest from 'supertest';
import app from '../src/index';

const request = supertest(app);

/**
 * @description colleciton of test cases on task request
 * @param {string} - case name
 */
describe('routes', () => {
    describe('GET /task', () => {
        it('should respond with a json', async () => {
            const { status, body: response } = await request.get('/task');
            expect(status).toBe(200);
            expect(Array.isArray(response.data)).toBeTruthy();
        });
    });
});

♻️Ambas librerías para pruebas, necesitamos obtener un servidor http y express no los retorna cuando utilizamos express().

import express from 'express';
import task from './routes/api/task';

// code
const app = express();

// code
app.use('/task', task);

// code
if (mode !== 'test')
    app.listen(port, () => listen(`⬢ Server Thingst - ${mode}`));
export default app;

🔥Evitamos que en test no levante el servidor, ya que supertest lo hará por nosotros.
.
♻️Actualizamos nuestro script para leer nuestros test:

"test": "NODE_ENV=test jest test/index.test.js --forceExit",

¿Recuerdan el documento docker-compose.yml?
Pues resulta que también le podemos colocar las variables de entorno de la siguiente manera, sin configuraciones o paquetes adicionales:

version: "3.3"

services:
  postgres:
    image: postgres:13
    environment:
      - POSTGRES_DB=${DB_NAME}
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    ports:
      - ${DB_PORT}:${DB_PORT}
    volumes:
      - /postgres_data:/var/lib/postgresql/data

  pgadmin:
    image: dpage/pgadmin4
    environment:
      - PGADMIN_DEFAULT_EMAIL=${PG_EMAIL}
      - PGADMIN_DEFAULT_PASSWORD=${PG_PASSWORD}
    ports:
      - ${PG_PORT}:${PG_REFPORT}

Hagan la prueba y me cuentan si funciona. 😉

Variables de ambiente en Node.js

Las variables de entorno se utilizan para contener contenido sensible

Instalamos el siguiente paquete

Esto nos sirve para tener las variables de entorno corriendo en el proceso de node

npm i dotenv

Creamos nuestro archivo de configuracion

config>config.js

require('dotenv').config();

const config = {
  env: process.env.NODE_ENV || 'dev',
  port: process.env.PORT || 3000,
  dbUser: process.env.DB_USER,
  dbPassword: process.env.DB_PASSWORD,
  dbHost: process.env.DB_HOST,
  dbName: process.env.DB_NAME,
  dbPort: process.env.DB_PORT,
};

module.exports = { config };

Adaptamos nuestro pool

Archivo anterior

const { Pool } = require('pg');

const pool = new Pool({
  host: 'localhost',
  port: 5432,
  user: 'kevin',
  password: 'admin123',
  database: 'my_store',
});

module.exports = pool;

Archivo usando las variables de entorno

La URI se conforma por “protocolo😕/USUARIO:CONTRASEÑA@HOST:PUERTO/NOMBREDB

El encodeURI es una manera de proteger estos datos tan sensibles, lo mismo cuando ya tenemos la URI armada

const { Pool } = require('pg');
const { config } = require('../config/config');

const USER = encodeURIComponent(config.dbUser);
const PASSWORD = encodeURIComponent(config.dbPassword);
const URI = `postgres://${USER}:${PASSWORD}@${config.dbHost}:${config.dbPort}/${config.dbName}`;

const pool = new Pool({ connectionString: URI });

module.exports = pool;

Archivo .env

	PORT=3000
  DB_USER='kevin'
  DB_PASSWORD='admin123'
  DB_HOST='localhost'
  DB_NAME='my_store'
  DB_PORT='5432'

Archivo .env.example

	PORT='',
  DB_USER='',
  DB_PASSWORD'',
  DB_HOST'',
  DB_NAME='',
  DB_PORT='',

Aplicando desestructuración de objetos podemos evitar repetir código.

Les dejo el archivo config.js por si no quieren escribir 😃


const config = {
	env: process.env.NODE_ENV || 'dev',
	port: process.env.PORT || 3000,
	dbUser: process.env.DB_USER,
	dbPassword: process.env.DB_PASSWORD,
	dbHost: process.env.DB_HOST,
	dbPort: process.env.DB_PORT,
	dbName: process.env.DB_NAME,
};

module.exports = { config };

const URI = `postgres://${USER}:${PASSWORD}@${config.dbHost}:${config.dbPort}/${config.dbName}`;

utilize esta extension para que se vea bonito el archivo .env
DotENV

un pequeño aporte que quizas le sirva a alguno, en las variables de entorno, en el archivo .env, no se debe poner coma al final. La coma hace que node tome la variable como si la coma fuera parte de la asignación y claramente genera error

<code> 
PORT = 3000
DB_USER='juan'
DB_PASSWORD='admin123'
DB_HOST='localhost'
DB_NAME='my_store'
DB_PORT='5432'

Me siento como si estuviese construyendo Laravel desde cero pero con JavaScript. Un Taylor Otwell.

Si alguien movio su archivo .env de la carpeta raiz es posible que les tire este error al momento de hacer peticiones:

node:internal/process/promises:279
            triggerUncaughtException(err, true /* fromPromise */);
            ^

Error: getaddrinfo ENOTFOUND undefined
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:109:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'undefined'
}

De ser asi, con regresar el archivo .env a la carpeta raiz debe volver a funcionarles correctamente.

Este man sabe mucho

A alguien mas el dotenv le crea un archivo con el puerto?

Por si alguien no lo sabe, para que las variables de entorno tengan efecto en el código, es necesario reiniciar el servidor de node.js.

En config.js se tiene la configuración base para leer las variables de entorno.

En node se leen las variables de entorno con process.env.NODE_ENV seguido del entorno process.env.NODE_ENV || ‘dev’.

require('dotenv').config();

const config = {
  env: process.env.NODE_ENV || 'dev',
  port: process.env.PORT || 3000,
  dbUser: process.env.DB_USER,
  dbPassword: process.env.DB_PASSWORD,
  dbHost: process.env.DB_HOST,
  dbName: process.env.DB_NAME,
  dbPort: process.env.DB_PORT,
};

module.exports = { config };

En el archivo postgres.pool.js se trae la configuración, una configuración es no enviar variable por variable, más bien, protegerlas un poco usando encodeURIComponent() y mandar una URI con todo el esquema de conexión.

const { Pool } = require('pg');
const { config } = require('../config/config');

const USER = encodeURIComponent(config.dbUser);
const PASSWORD = encodeURIComponent(config.dbPassword);
const URI = `postgres://${USER}:${PASSWORD}@${config.dbHost}:${config.dbPort}/${config.dbName}`;

const pool = new Pool({ connectionString: URI });

module.exports = pool;

En el ambiente de desarrollo, se crea un archivo de variables de entorno .env (archivo delicado, no se sube a github), ahí se indican los datos sensibles de la BD u otras configuraciones de ejecución. Una buena practica es tener un archivo .env.example el cual indica un ejemplo de cómo deberían ir las variables de entorno.

La librería dotenv nos ayuda a leer el archivo .env . Para usarla, primero se requiere en el archivo config.js y lo que hace por defecto es leer el archivo .env y cargarlas al proceso de node.

dotenv ya no es necesario:
Para las versiones más recientes de Node (20.6.0 y posteriores) ya no es necesaria la librería dotenv. Puedes simplemente agregar la flag --env-file=.env a los scripts

"dev": "node --watch --env-file=.env index.js",
"start": "node --env-file=.env index.js"

Nota: Desde la versión 18.11.0 se agregó a Node el modo watch como una feature experimental, por lo que, de momento, Nodemon tampoco es necesario. Agregando la flag --watch puedes activar el modo.

Para los que usan ES modules y no CommonJS ```js import pkg from 'pg'; import dotenv from 'dotenv'; // Cargar las variables de entorno desde el archivo .env dotenv.config(); const { Pool } = pkg; // Configurar el pool de conexiones de PostgreSQL usando las variables de entorno export const pool = new Pool({ user: process.env.DB_USER, password: process.env.DB_PASSWORD, host: process.env.DB_HOST, database: process.env.DB_NAME, port: process.env.DB_PORT, }); ```
Les recomiendo que si tienen mucho problema con el docker, es decir, con los errores es mejor utilizar el mismo PostgreSQL instalado en el sistema, crear una base de datos y conectarse a ella de forma local para no estar como yo que en 3dias estuve buscando un error para solucionarlo y no tener lograr solucionar

lo más recomendado es que a su archivo postgres.js también utilicen las variables de entorno, les dejo la forma en que yo lo hice:

const { Client } = require('pg');
const { config } = require('./../config/config');

const USER = encodeURIComponent(config.dbUser);
const PASSWORD = encodeURIComponent(config.dbPassword);
const HOST = encodeURIComponent(config.dbHost);
const DATABASE = encodeURIComponent(config.dbName);
const PORT = encodeURIComponent(config.dbPort);
const URI = `postgres://${USER}:${PASSWORD}@${HOST}:${PORT}/${DATABASE}`;

async function getConnection() {
    const client = new Client({ connectionString: URI});
    await client.connect();
    return client;
}

module.exports = getConnection;

La encodeURIComponent()función codifica un URI reemplazando cada instancia de ciertos caracteres por una, dos, tres o cuatro secuencias de escape que representan la codificación UTF-8 del carácter (solo habrá cuatro secuencias de escape para caracteres compuestos por dos caracteres “sustitutos”).

Hola comunidad un saludo, Desde la versión 20.6.x en adelante ya no hace falta instalar la dependencia de **dotenv**, en el package.json se pasa el ***.env*** como parámetro. `"scripts": {` ` "dev": "node --env-file .env index.js"` `}` **Nodemon** tampoco es necesario desde la versión 22.x ahora podemos usar ***--watch*** `"scripts": {` ` "dev": "node --watch --env-file .env index.js"` `}`
me di cuenta del error antes que el profe, algo debo estar haciendo bien jjaja

El URI identifica un recurso y lo diferencia de otros mediante un nombre, una ubicación o ambos. La URL identifica la dirección web o la ubicación de un recurso único.

encodeURI y encodeURIComponent Las URL solo pueden tener ciertos caracteres de los 128 set de caracteres estándar ASCII. Se debe codificar los caracteres reservados que no pertenezcan a este set. Los caracteres especiales admitidos por UTF-8. Esto significa que es necesario codificar estos caracteres especiales cuando pasan a una URL. Cuando los caracteres especiales como &, space, á, é, í, ó, ú, ñ, ü, etc. entran en una URL, se tienen que reemplazar mediante la codificación, ya que si no, pueden causar situaciones impredecibles.

Resumen: Si tienes una URL completa, utiliza encodeURI. Pero si tienes una parte de una URL, utiliza encodeURIComponent.

Si estan usando Mysql quedaria algo asi.const { Sequelize } = require('sequelize');const { config } = require('../config/environment'); const sequelize = new Sequelize(config.DB\_NAME, config.DB\_USER, config.DB\_PASSWORD, {  host: config.DB\_HOST,  port: config.DB\_PORT,  dialect: 'mysql',  logging: false,  pool: {    max: 5,    min: 0,    acquire: 30000,    idle: 10000  }}); *module*.*exports* = { sequelize } ```js const { Sequelize } = require('sequelize'); const { config } = require('../config/environment'); const sequelize = new Sequelize(config.DB_NAME, config.DB_USER, config.DB_PASSWORD, { host: config.DB_HOST, port: config.DB_PORT, dialect: 'mysql', logging: false, pool: { max: 5, min: 0, acquire: 30000, idle: 10000 } }); module.exports = { sequelize } ```

Tambien puedes guardar las variables de docker-compose en el archivo .env y asi compartir las credencias en el docker y en express sin tener que duplicar las credencias ni dejarlas en el codigo:

version: '3.3'

services:
  postgres:
    container_name: db.henryjperez
    image: postgres
    env_file:
      - .env
    ports:
      - "${POSTGRES_PORT}:${POSTGRES_PORT}"
    volumes:
      - ./db_data:/var/lib/postgresql/data

  pgadmin:
    container_name: pgadmin
    depends_on:
      - postgres
    image: dpage/pgadmin4
    env_file:
      - .env
    ports:
      - 5050:80

Parte del config.js:

// Postgres
export const postgres_db = process.env.POSTGRES_DB;
export const postgres_user = process.env.POSTGRES_USER;
export const postgres_password = process.env.POSTGRES_PASSWORD;
export const postgres_port = process.env.POSTGRES_PORT;
export const postgres_host = process.env.POSTGRES_HOST;

paquete dot env: para leer las variables de entorno

instalamos dependencias

npm install dotenv 

creamos variables de ambiente y variables de ambiente de ejemplo
.env:

PORT=3000
DB_USER='jclentino'
DB_PASSWORD='toor'
DB_HOST='localhost'
DB_NAME='my-store'
DB_PORT='5432'

.env.example:

PORT=3000
DB_USER=''
DB_PASSWORD=''
DB_HOST=''
DB_NAME=''
DB_PORT=''

creamos un objeto de configuración con las variables de entorno
config/config.js:

require('dotenv').config()

const config = {
  env: process.env.NODE_ENV || 'dev',
  port: process.env.PORT,
  dbUser: process.env.DB_USER,
  dbPassword: process.env.DB_PASSWORD,
  dbHost: process.env.DB_HOST,
  dbName: process.env.DB_NAME,
  dbPort: process.env.DB_PORT,
}

module.exports = { config }

importamos el objeto de configuración en el archivo que contiene el pool de conexiones y reemplazamos la conexión por un string de conexión
libs/postgres.pool.js:

const { Pool } = require('pg')
const { config } = require('../config/config')

const USER = encodeURIComponent(config.dbUser)
const PASSWORD = encodeURIComponent(config.dbPassword)
const URI = `postgres://${USER}:${PASSWORD}@${config.dbHost}:${config.dbPort}/${config.dbName}`

const pool = new Pool({ connectionString: URI })

module.exports = pool

corremos el proyecto

npm run dev 

si visitamos la url localhost:3000/api/v1/products/ veremos como respuesta los registros en la tabla tasks

[
  {
    "id": 1,
    "title": "Terminar curso de postgres ",
    "completed": false
  },
  {
    "id": 2,
    "title": "Limpiar ",
    "completed": false
  }
]

hola, si a alguien le aparece un error Error: getaddrinfo ENOTFOUND traten de revisar la URI o la sintaxis de nuevo
o fíjense de que {config} este desestructurado XD

Solo como comentario, crear un carpeta para un solo archivo se me hace algo innecesario, de igual forma al crearla me parece sería mejor nombrar el archivo como index.js asi los imports quedan de la misma maneta que si solo crearas el archivo config.js en raíz y son igualmente legibles y ordenados.