Cómo usar PostgreSQL con Node.js

Cómo configurar una conexión agrupada a PostgreSQL en Node.js y una función conveniente para ejecutar conexiones a través de ese grupo.

Primeros pasos

Debido a que el código que estamos escribiendo para este tutorial es "independiente" (lo que significa que no es parte de una aplicación o proyecto más grande), vamos a crear un proyecto de Node.js desde cero. Si aún no tiene Node.js instalado en su computadora, lea este tutorial primero y luego regrese aquí.

Una vez que haya instalado Node.js en su computadora, desde la carpeta de proyectos en su computadora (por ejemplo, ~/projects ), crea una nueva carpeta para nuestro trabajo:

Terminal

mkdir postgresql

A continuación, cd en ese directorio y crea un index.js archivo (aquí es donde escribiremos nuestro código para el tutorial):

Terminal

cd postgresql && touch index.js

A continuación, queremos instalar dos dependencias, pg y express :

Terminal

npm i pg express

El primero nos dará acceso al controlador Node.js para PostgreSQL (lo que usaremos para conectarnos a la base de datos en nuestro código) y el segundo, Express, se usará para activar un servidor de demostración.

Un último paso:en el package.json archivo que se creó para usted, asegúrese de agregar el campo "type": "module" como propiedad. Esto habilitará la compatibilidad con ESModules y nos permitirá usar el import declaraciones que se muestran en el siguiente código.

Con eso en su lugar, estamos listos para comenzar.

Instalación y configuración de PostgreSQL

Antes de comenzar a escribir código, debemos asegurarnos de que tiene PostgreSQL instalado en su máquina y que PostgreSQL se agregó correctamente al PATH de su línea de comando variable (esto crea accesos directos a carpetas en su computadora y los hace accesibles desde cualquier ubicación/directorio en su línea de comando).

El mejor lugar para comenzar es en la página de descargas de PostgreSQL. Desde aquí, seleccione su sistema operativo y en la página siguiente, ubique el enlace rojo "Descargar el instalador" cerca de la parte superior de la página.

Después de descargar el instalador, ejecútelo y complete los pasos en pantalla. Asegúrese de instalar todas las dependencias necesarias (se prefiere lo que esté marcado de forma predeterminada en la interfaz de usuario del instalador para evitar problemas).

Nota :si está en MacOS, esto debería ser todo lo que necesita hacer antes de continuar con la siguiente parte del tutorial.

Si estás en Windows debe completar un paso adicional:agregar la carpeta de la versión de PostgreSQL a su RUTA.

Para hacer esto, en el cuadro "Escriba aquí para buscar" en su barra de inicio, escriba "env" y haga clic en el enlace para el resultado "Editar las variables de entorno del sistema". En la ventana resultante "Propiedades del sistema", localice y haga clic en el botón "Variables de entorno..." en la parte inferior derecha de la ventana.

En el cuadro denominado "Variables de usuario para <username> , busque la fila "Ruta", haga clic para resaltarla y luego presione el botón "Editar..." debajo de la lista.

En la ventana "Editar variable de entorno" que aparece, haz clic en el botón "Nuevo" en el lado derecho de la ventana y en el campo de texto que aparece, escribe C:\Program Files\PostgreSQL\14\bin . Tenga en cuenta :el 14 en esta ruta representa la última versión de PostgreSQL que debería ser instalado en su computadora (a partir de la escritura). Es posible que deba ajustarse en función de cuándo lea este tutorial. Se recomienda que navegue hasta el C:\Program Files\PostgreSQL carpeta y busque el número de versión más nuevo/más alto en eso carpeta para usar en esta ruta.

Una vez configurado, haga clic en "Aceptar" en cada una de las ventanas que aparecieron hasta ahora. Se recomienda que reinicie su computadora después de hacer esto para asegurarse de que las variables se carguen correctamente en su línea de comando.

Una vez que haya reiniciado su computadora, estará listo para continuar con el tutorial.

Agregar comandos auxiliares de PostgreSQL a package.json

Primero, debemos asegurarnos de que tenemos un servidor PostgreSQL en ejecución y una base de datos on ese servidor Para hacer esto un poco más fácil, vamos a comenzar abriendo el package.json archivo en la raíz de nuestro proyecto.

/paquete.json

{
  "name": "ny290syhfjifjekd",
  "type": "module",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "pg:init": "pg_ctl init -D data",
    "pg:start": "pg_ctl -D data start",
    "pg:createdb": "createdb -h 127.0.0.1 app",
    "pg:stop": "pg_ctl -D data stop",
    "start": "NODE_ENV=development && node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.3",
    "pg": "^8.7.3"
  }
}

Nuestro objetivo aquí es agregar algunos comandos de "ayuda" al scripts sección de nuestro package.json expediente. Específicamente, necesitamos agregar cinco comandos:

  1. pg:init que inicializará el directorio de datos donde PostgreSQL almacenará nuestros datos.
  2. pg:start que iniciará el servidor PostgreSQL.
  3. pg:createdb que creará una base de datos PostgreSQL en el servidor.
  4. pg:stop que detendrá el servidor PostgreSQL.
  5. start que usaremos para iniciar nuestro servidor de demostración a través de Express.

Tenga en cuenta que para el pg scripts prefijados, estamos usando un comando pg_ctl o createdb . Anteriormente, cuando instalamos PostgreSQL, estos comandos estaban expuestos a su variable PATH, lo que significa que son accesibles globalmente en su terminal (si está en Windows, asegúrese de haber completado los pasos adicionales anteriores para que esto funcione).

Una vez que los tenga en su lugar, queremos ejecutar lo siguiente a través de la terminal, desde la raíz de la carpeta del proyecto que creamos anteriormente:

Terminal

mkdir data

A continuación, para inicializar nuestro servidor PostgreSQL, ejecute:

Terminal

npm run pg:init

Debería ver un mensaje que indica que esto se ha realizado correctamente después de unos segundos. A continuación, queremos iniciar el servidor:

Terminal

npm run pg:start

Esto mostrará algunos resultados y señalará que el servidor se inició correctamente. Finalmente, para crear nuestra base de datos queremos ejecutar:

Terminal

npm run pg:createdb

Esto creará una base de datos llamada app en el servidor que acaba de iniciar, que es la base de datos que usaremos en nuestros ejemplos a continuación.

Crear un grupo de conexiones

Suponiendo que todo eso funcionó, ahora necesitamos configurar nuestra conexión a PostgreSQL a través de nuestro código Node.js. Para comenzar, queremos crear un nuevo archivo en la raíz de nuestro proyecto, postgresql.js :

/postgresql.js

import postgresql from 'pg';

const { Pool } = postgresql;

export default (callback = null) => {
  // We'll handle our connection to PostgreSQL here...
};

En este nuevo archivo, para comenzar, queremos importar la exportación predeterminada del pg paquete que instalamos anteriormente como postgresql . A continuación, justo debajo de la importación, queremos "arrancar" el Pool (sensible a mayúsculas y minúsculas) usando la desestructuración de JavaScript (indicado por el {} después del const ).

Aquí, Pool es una clase que nos permite crear un agrupado conexión a nuestra base de datos PostgreSQL. Esto es importante . En una aplicación web multiusuario, queremos ser lo más eficientes posible al hablar con nuestra base de datos. Crear una conexión por solicitud significa que podemos abrumar fácilmente nuestra base de datos, lo que podría generar tiempo de inactividad.

Cuando usamos PostgreSQL, podemos usar la agrupación característica que crea un "grupo" de conexiones que un usuario puede ocupar temporalmente y luego regresar al grupo cuando finaliza (piense en esto como un libro de la biblioteca que se presta y luego se devuelve).

Terminal

import postgresql from 'pg';
import os from 'os';

const { Pool } = postgresql;

export default (callback = null) => {
  // NOTE: PostgreSQL creates a superuser by default on localhost using the OS username.
  const pool = new Pool({
    user: process.env.NODE_ENV === 'development' && (os.userInfo() || {}).username || '',
    database: 'app',
    password: '',
    host: '127.0.0.1',
    port: 5432,
  });

  // We'll handle making the connection accessible in our app here...
};

Con el Pool clase accesible, dentro de la función que estamos exportando desde nuestro archivo, queremos crear un new instancia de él y asígnelo a la variable pool (minúsculas, aquí). Esta variable, pool contendrá la "instancia de grupo" y es lo que usaremos como punto de partida para conectarnos a nuestra base de datos PostgreSQL.

Al new Pool() clase, pasamos un objeto de opciones que contiene la información de conexión para la base de datos. Aquí, debido a que acabamos de iniciar nuestro servidor PostgreSQL localmente, configuramos el host en 127.0.0.1 (la versión de la dirección IP de localhost ) y el puerto a 5432 (el puerto predeterminado para PostgreSQL).

También configuramos el database a "aplicación" (la que acabamos de crear con el db:createdb script) y establezca la contraseña en una cadena vacía. Para el user , hacemos algo interesante.

De forma predeterminada, PostgreSQL crea un superusuario localmente para nosotros usando el nombre de usuario actual en el sistema operativo (por ejemplo, mi nombre de usuario es rglover en mi computadora portátil, por lo que PostgreSQL creó el nombre de usuario rglover ).

Si bien podríamos codificar esto, hace que nuestro código sea bastante inflexible. Para evitar esto, arriba, hemos agregado una importación adicional para el os paquete que es un módulo central de Node.js que nos da acceso a información sobre el sistema operativo. Aquí, asumiendo nuestro NODE_ENV es development (notará que configuramos esto como parte del start script que definimos anteriormente), llamamos al os.userInfo() función que esperamos devolver un objeto que describa el usuario actual del sistema operativo.

En ese objeto, el username coincidirá con el usuario conectado actualmente en la computadora (el mismo valor que utilizará PostgreSQL para crear el superusuario). El (os.userInfo() || {}).username parte aquí es un truco de seguridad:en caso de que os.userInfo() no devolvió nada, queremos recurrir a un objeto vacío para no causar un error de tiempo de ejecución accidental si obtenemos null o undefined (usted no tiene hacer esto, pero hace que nuestro código sea un poco más tolerante a los errores).

Con esto, ahora tenemos nuestra conexión de grupo, pero aún no hemos terminado. Para que esta conexión sea útil, debemos hacerla accesible para toda nuestra aplicación/código base.

Terminal

import postgresql from 'pg';
import os from 'os';

const { Pool } = postgresql;

export default (callback = null) => {
  // NOTE: PostgreSQL creates a superuser by default on localhost using the OS username.
  const pool = new Pool({
    user: process.env.NODE_ENV === 'development' && (os.userInfo() || {}).username || '',
    database: 'app',
    password: '',
    host: '127.0.0.1',
    port: 5432,
  });

  const connection = {
    pool,
    query: (...args) => {
      return pool.connect().then((client) => {
        return client.query(...args).then((res) => {
          client.release();
          return res.rows;
        });
      });
    },
  };

  process.postgresql = connection;

  if (callback) {
    callback(connection);
  }

  return connection;
};

Justo debajo de nuestra llamada a new Pool() , aquí hemos agregado una nueva variable connection establecer igual a un objeto con dos valores en él:pool (nuestro nuevo pool instancia devuelta por new Pool() ) y query .

Debajo de esta definición de objeto, observe que en Node.js process , estamos agregando una propiedad postgresql y asignándolo a este connection objeto. Esto nos dará acceso global a nuestra conexión de grupo a través de toda nuestra aplicación (el process se puede acceder al objeto en todo nuestro código base).

Volviendo a centrarnos en el query parte, esta es una función especial que agregamos para nuestra conveniencia. Cuando usamos un grupo de conexiones, cada vez que queremos realizar una consulta, debemos conectarnos a ese grupo, ejecutar nuestra consulta y luego devolver o "liberar" la conexión al grupo.

Si bien está perfectamente bien hacer esto, puede ser engorroso. Para hacer nuestro trabajo un poco más fácil, aquí, el query propiedad que estamos configurando en connection "automatiza" ese proceso de conexión y liberación. Primero, asignamos query a una función simple de JavaScript y use JavaScript rest operador ... para decir "recoja los argumentos pasados ​​a esta función en una variable llamada args cuyo alcance es el cuerpo de la función que estamos definiendo."

Dentro de esa función, devolvemos una llamada a pool.connect() que a su vez devuelve una promesa de JavaScript. Cuando esa Promesa se resuelva , esperamos que pase un client conexión. En esa conexión, podemos realizar consultas a nuestra base de datos PostgreSQL, por lo que llamamos client.query() pasando el ...args valor de nuestra función contenedora. Aquí, ...args se denomina "difusión" ya que estamos "difundiendo" el valor de args como los argumentos que se pasan a client.query() .

Entonces, asumiendo que llamamos a la función que estamos asignando a query como query('SELECT * FROM books') , estaríamos efectivamente escribiendo client.query('SELECT * FROM books') . El ...args part simplemente automatiza el proceso de mover todos los argumentos pasados ​​a una función y luego "pasarlos" a otra función (u objeto).

Después de client.query() se llama, esperamos eso para devolver una respuesta de la base de datos, y en esa respuesta, una propiedad llamada rows que es una matriz de filas de nuestra base de datos que coinciden con nuestra consulta (si hay alguna).

Igual que pool.connect() esperamos client.query() para devolver una promesa de JavaScript. Aquí, en el .then() función de devolución de llamada (lo que se llama después de que se resuelve la Promesa/nuestra consulta se completa), hacemos una llamada a client.release() para volver a poner nuestra conexión en el grupo y luego devolver res.rows . Esto asegura que el valor de res.rows "burbujea" a la llamada original a query .

Siguiendo el patrón aquí, esperaríamos poder hacer algo como esto:

const rows = await process.postgresql.query('SELECT * FROM books');
console.log(rows);
/*
  [{ id: 1, title: 'The Best Book Ever', author: 'Author McAuthorstuff' }]
*/

Esto es exactamente lo que pretendemos conectar a continuación. Antes de terminar con este archivo, queremos llamar la atención sobre el callback argumento que se pasa a la función que estamos exportando desde este archivo. Si está definido, después de establecer nuestra conexión en process , queremos llamar a esa función y pasarle nuestro connection objeto. Veremos por qué a continuación.

Configuración de una aplicación de demostración

Ahora estamos listos para usar nuestra conexión. Para hacerlo, configuraremos una aplicación barebones Express.js, sembraremos nuestra base de datos con algunos datos de prueba y luego conectaremos un punto final Express donde podemos probar una llamada al query función que acabamos de definir arriba.

/index.js

import express from 'express';
import postgresql from 'postgresql';

postgresql();

const app = express();

app.get('/books', async (req, res) => {
  const rows = await process.postgresql.query('SELECT * FROM books');
  res.status(200).send(JSON.stringify(rows));
});

app.listen(3000, () => {
  console.log('App running at http://localhost:3000');
});

Esto es todo lo que necesitamos. Aquí, importamos express del express paquete que instalamos anteriormente y creamos una nueva instancia llamándolo como una función express() , almacenándolo en la variable app .

A continuación, centrándonos primero en la parte inferior, llamamos a app.listen() que le dice a Express que comience a escuchar solicitudes HTTP en el puerto 3000 de nuestra computadora (para indicar que este proceso se ha completado, agregamos una función de devolución de llamada para cerrar sesión y un mensaje que nos informa que el servidor comenzó).

Por encima de esto, definimos una ruta HTTP GET en la URL /books (esto será accesible en el navegador en http://localhost:3000/books ). Dentro del controlador de devolución de llamada de esa ruta, asumimos que tendremos acceso a nuestro process.postgresql valor que asignamos en /postgresql.js (lo que estamos importando arriba y llamando a la función exportada de arriba de nuestra llamada a express() ).

Finalmente, con el resultado rows esperamos respuesta de nuestro query función de conveniencia, res responder a la solicitud inicial, devolviendo una copia de las filas en cadenas.

En tu terminal, desde la raíz de nuestro proyecto, si ejecutamos npm start , deberíamos ver el mensaje "Aplicación en ejecución..." impreso en la consola. Si visitamos esa ruta http://localhost:3000/books en un navegador, deberíamos ver una matriz vacía impresa en la pantalla.

Si lo hace, esto significa que nuestra conexión con PostgreSQL está funcionando y, técnicamente hablando, nuestro trabajo está completo.

Sin embargo, antes de terminar, sería útil ver algunos datos reales. Para hacer eso, necesitamos aprovechar la función de devolución de llamada que anticipamos en /postgresql.js .

Sembrando la base de datos

En una aplicación, el proceso de generar datos de prueba se conoce vagamente como "sembrar la base de datos". Por lo general, creará un "accesorio", que es un código que automatiza el proceso de inicialización (compórtese).

/index.js

import express from 'express';
import postgresql from './postgresql.js';

postgresql(async (connection) => {
  await connection.query('CREATE TABLE IF NOT EXISTS books (id bigserial primary key, title text, author text);');
  await connection.query('CREATE UNIQUE INDEX IF NOT EXISTS title ON books (title);');

  const books = [
    { title: 'Mastering the Lightning Network', author: 'Andreas Antonopoulos' },
    { title: 'Load Balancing with HAProxy', author: 'Nick Ramirez' },
    { title: 'Silent Weapons for Quiet Wars', author: 'Unknown' },
  ];

  for (let i = 0; i < books.length; i += 1) {
    const book = books[i];
    await connection.query(`INSERT INTO books (title, author) VALUES ('${book.title}', '${book.author}') ON CONFLICT DO NOTHING;`);
  }

  console.log('PostgreSQL database seeded!');
});

const app = express();

app.get('/books', async (req, res) => {
  const rows = await process.postgresql.query('SELECT * FROM books');
  res.status(200).send(JSON.stringify(rows));
});

app.listen(3000, () => {
  console.log('App running at http://localhost:3000');
});

Aquí, hemos agregado una función de devolución de llamada a nuestra llamada a postgresql() , esperando un connection objeto que se va a pasar como argumento. En esa conexión, necesitamos ejecutar tres consultas:

  1. Una consulta para crear una tabla llamada books en nuestra base de datos si no existe.
  2. Una consulta para crear un índice único en el title columna de nuestro books mesa.
  3. Para cada libro con el que queremos "sembrar" la base de datos, una consulta para insertar ese libro si aún no existe.

Las consultas específicas y el código aquí no son muy importantes. Lo principal a llamar la atención es nuestro uso del query función de conveniencia que conectamos. No solo podemos llamarlo para obtener datos de vuelta a cambio, pero también podemos usarlo para ejecutar consultas arbitrarias que no esperan un valor de retorno.

Aquí, hacemos exactamente eso, configuramos nuestra tabla y un índice único en nuestro título (esto evita que los reinicios de la aplicación creen duplicados) y luego recorremos nuestra matriz de books , realizando un INSERT consulta para cada libro en el books mesa.

Ahora, si reiniciamos nuestra aplicación y luego cargamos el http://localhost:3000/books ruta en el navegador, deberíamos ver nuestros tres libros devueltos.

Terminando

En este tutorial, aprendimos cómo configurar y conectarnos a una base de datos PostgreSQL usando Node.js. Aprendimos cómo hacer que PostgreSQL funcione en nuestra computadora, cómo escribir algunos scripts NPM para ayudarnos a configurar nuestra base de datos y cómo escribir un módulo con una función conveniente para conectarse a nuestro servidor PostgreSQL como un grupo de conexiones y ejecutar consultas. Finalmente, aprendimos cómo sembrar la base de datos con algunos datos de prueba y configurar una ruta de prueba en Express.js para verificar que nuestra función de conveniencia estaba funcionando.