¿Cuál es la forma correcta de usar el módulo postgresql de node.js?

95

Estoy escribiendo una aplicación node.js en Heroku y usando el módulo pg . No puedo encontrar la forma "correcta" de obtener un objeto de cliente para cada solicitud que necesito para consultar la base de datos.

La documentación usa un código como este:

pg.connect(conString, function(err, client) {
  // Use the client to do things here
});

Pero seguramente no necesita llamar pg.connectdentro de cada función que usa la base de datos, ¿verdad? He visto otro código que hace esto:

var conString = process.env.DATABASE_URL || "tcp://postgres:1234@localhost/postgres";
var client = new pg.Client(conString);
client.connect();
// client is a global so you can use it anywhere now

Me estoy inclinando hacia la segunda opción porque creo que la instancia de base de datos gratuita para Heroku está limitada a una conexión de todos modos, pero ¿hay algún inconveniente en hacerlo de esta manera? ¿Debo comprobar si mi objeto cliente todavía está conectado cada vez antes de usarlo?

Felipe
fuente

Respuestas:

158

Soy el autor de node-postgres . Primero, me disculpo porque la documentación no ha dejado clara la opción correcta: es mi culpa. Intentaré mejorarlo. Escribí un Gist justo ahora para explicar esto porque la conversación se hizo demasiado larga para Twitter.

Usar pg.connectes el camino a seguir en un entorno web.

El servidor PostgreSQL solo puede manejar 1 consulta a la vez por conexión. Eso significa que si tiene 1 global new pg.Client()conectado a su backend, toda su aplicación está atascada en función de la rapidez con la que Postgres puede responder a las consultas. Literalmente alineará todo, poniendo en cola cada consulta. Sí, es asíncrono y está bien ... pero ¿no preferirías multiplicar tu rendimiento por 10 veces? Use pg.connect establecer el pg.defaults.poolSizeen algo sensato (hacemos 25-100, no estamos seguros del número correcto todavía).

new pg.Clientes para cuando sabes lo que estás haciendo. Cuando necesita un solo cliente de larga duración por alguna razón o necesita controlar con mucho cuidado el ciclo de vida. Un buen ejemplo de esto es cuando se usa LISTEN/NOTIFY. El cliente que escucha debe estar cerca y conectado y no compartido para que pueda manejar correctamente los NOTIFYmensajes. Otro ejemplo sería al abrir un cliente único para eliminar algunas cosas colgadas o en scripts de línea de comandos.

Algo muy útil es centralizar todo el acceso a su base de datos en su aplicación en un archivo. No ensucie las pg.connectllamadas o los nuevos clientes en todo momento. Tener un archivo como db.jsese se parece a esto:

module.exports = {
   query: function(text, values, cb) {
      pg.connect(function(err, client, done) {
        client.query(text, values, function(err, result) {
          done();
          cb(err, result);
        })
      });
   }
}

De esta manera, puede cambiar su implementación pg.connecta un grupo personalizado de clientes o lo que sea y solo tiene que cambiar las cosas en un lugar.

Eche un vistazo al módulo node-pg-query que hace precisamente esto.

Brianc
fuente
2
Lo siento, soy bastante nuevo en DBMS y todavía tengo problemas para entender esto, pero ¿por qué no queremos "tirar basura pg.connect" llamadas? ¿Es por simplicidad o por motivos de rendimiento? Por ejemplo, llamo a pg.connect una vez en cada una de las rutas que tengo en mi aplicación básica (todas con la misma conString). ¿Esta bien? Intuitivamente, se siente como si estuviera haciendo una nueva conexión con la misma base de datos cada vez que lo llamo (lo cual no quiero), pero ¿usa las conexiones agrupadas internamente? Gracias.
user1164937
Increíble. ¿Por qué está empleando una conexión por consulta en lugar de una por solicitud? He estado buscando una forma adecuada de compartir una conexión a través de múltiples consultas dentro de una solicitud y había estado considerando res.locals antes de encontrar su respuesta aquí.
Joe Lapp
2
Oh espera. Parece que su solución aquí no admitirá transacciones.
Joe Lapp
Esto debería estar vinculado permanentemente al github.
Ryan Willis
1
Tenga en cuenta que pg.connect se eliminó después de la versión 7 de node-postgres, también conocido como pg. Ver stackoverflow.com/questions/45174120/pg-connect-not-a-function
Colin D
23

Soy el autor de pg-promise , que simplifica el uso de node-postgres a través de promises.

Aborda los problemas sobre la forma correcta de conectarse y desconectarse de la base de datos, utilizando el grupo de conexiones implementado por node-postgres , entre otras cosas, como transacciones automatizadas.

Una solicitud individual en pg-promise se reduce a lo que es relevante para la lógica de su negocio:

db.any('SELECT * FROM users WHERE status = $1', ['active'])
    .then(data => {
        console.log('DATA:', data);
    })
    .catch(error => {
        console.log('ERROR:', error);
    });

es decir, no necesita lidiar con la lógica de conexión al ejecutar consultas, porque configura la conexión solo una vez, globalmente, así:

const pgp = require('pg-promise')(/*options*/);

const cn = {
    host: 'localhost', // server name or IP address;
    port: 5432,
    database: 'myDatabase',
    user: 'myUser',
    password: 'myPassword'
};
// alternative:
// const cn = 'postgres://username:password@host:port/database';

const db = pgp(cn); // database instance;

Puede encontrar muchos más ejemplos en el tutorial Learn by Example o en la página de inicio del proyecto .

vitaly-t
fuente
Hola, Heroku solo acepta conexiones SSL. En pgesto está especificado por pg.defaults.ssl = true;. ¿Cómo haces esto en pg-promise?
ocram
@ocram github.com/vitaly-t/pg-promise/wiki/… , o puede especificar SSL dentro de los parámetros de conexión: github.com/vitaly-t/pg-promise/wiki/Connection-Syntax
vitaly-t
Soy nuevo en la mayor parte de esto: javascript, promesas, postgres, etc. y esto es exactamente lo que necesitaba. ¡¡Gracias!!
Ryan Rodemoyer
1
@ocram Acabo de hacer que esto funcione haciendopgp.pg.defaults.ssl = true;
CharlieC
¿Esto creará múltiples conexiones para mejorar el rendimiento de postgres automáticamente cuando le damos múltiples solicitudes de consulta a postgres?
domingo
5

La piscina es el camino a seguir ahora. Algo como esto

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

    const pool = new Pool({
      connectionString: DATABASE_URL,
      ssl: false,
      max: 20,
      idleTimeoutMillis: 30000,
      connectionTimeoutMillis: 2000,
    });
    module.exports = {
        query: (text, params) => pool.query(text, params)
      }

se puede usar como db.query('<BEGIN,COMMIT,ROLLBACK,your query,anything')

amar
fuente
1

Es mejor crear un grupo de pg globalmente y cada vez que necesite hacer una operación de base de datos, use el cliente y luego vuelva a enviarlo al grupo. Una vez que se realizan todas las operaciones de db, finalice el grupo usandopool.end()

Código de muestra -

let pool = new pg.Pool(dbConfig);
pool.connect(function(err, client, done) {

if (err) {
    console.error('Error connecting to pg server' + err.stack);
    callback(err);
} else {
    console.log('Connection established with pg db server');

    client.query("select * from employee", (err, res) => {

            if (err) {
                console.error('Error executing query on pg db' + err.stack);
                callback(err);
            } else {
                console.log('Got query results : ' + res.rows.length);


               async.each(res.rows, function(empRecord) {   
                        console.log(empRecord.name);
                });
            }
            client.release();

        });
}

});  

Para obtener más detalles, puede consultar la publicación de mi blog - Fuente

Aniket Thakur
fuente
-1

Estaba interesado en un controlador muy simple para esto, así que hice el mío sin complicarlo demasiado. No me hago ilusiones de que sea súper básico, pero podría ayudar a algunas personas a comenzar. Básicamente, se conecta, ejecuta consultas y maneja errores por usted.

function runQuery(queryString, callback) {
  // connect to postgres database
  pg.connect(postgresDatabase.url,function(err,client,done) {
    // if error, stop here
    if (err) {console.error(err); done(); callback(); return;}
    // execute queryString
    client.query(queryString,function(err,result) {
      // if error, stop here
      if (err) {console.error(err+'\nQuery: '+queryString); done(); callback(); return;}
      // callback to close connection
      done();
      // callback with results
      callback(result.rows);
    });
  });
}

Entonces lo usaría llamándolo de esta manera:

runQuery("SELECT * FROM table", function(result) {
  // Whatever you need to do with 'result'
}
JM-AGMS
fuente
Esto ni siquiera libera la conexión al grupo. Agotará la piscina muy rápido. El ejemplo básico en la node-postgrespágina funciona mejor que esto.
vitaly-t
-2

Así es como lo hago, una especie de "enfoque de todos los anteriores"

Promise = require 'bluebird'
pg = module.exports = require 'pg'

Promise.promisifyAll pg.Client.prototype
Promise.promisifyAll pg.Client
Promise.promisifyAll pg.Connection.prototype
Promise.promisifyAll pg.Connection
Promise.promisifyAll pg.Query.prototype
Promise.promisifyAll pg.Query
Promise.promisifyAll pg

connectionString = process.env.DATABASE_URL

module.exports.queryAsync = (sql, values) ->
  pg.connectAsync connectionString
  .spread (connection, release) ->
    connection.queryAsync sql, values
    .then (result) ->
      console.log result.rows[0]
    .finally ->
      release()
Campos de Duane
fuente
1
Por lo tanto, terminará sin administración de conexiones, sin soporte de transacciones y sin soporte de tareas. ¿Qué sentido tiene entonces?
vitaly-t
1
¿qué idioma es ese? ¿café? berk
caub