Quelle est la bonne façon d'utiliser le module postgresql node.js?


95

J'écris une application node.js sur Heroku et j'utilise le module pg . Je ne peux pas trouver la "bonne" façon d'obtenir un objet client pour chaque demande dont j'ai besoin pour interroger la base de données.

La documentation utilise un code comme celui-ci:

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

Mais vous n'avez sûrement pas besoin d'appeler à l' pg.connectintérieur de chaque fonction qui utilise la base de données, n'est-ce pas? J'ai vu un autre code qui fait ceci:

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

Je penche vers la deuxième option car je pense que l'instance de base de données gratuite pour Heroku est de toute façon limitée à une connexion, mais y a-t-il des inconvénients à le faire de cette façon? Dois-je vérifier si mon objet client est toujours connecté à chaque fois avant de l'utiliser?

Réponses:


158

Je suis l'auteur de node-postgres . Tout d'abord, je m'excuse que la documentation n'a pas réussi à préciser la bonne option: c'est ma faute. Je vais essayer de l'améliorer. J'ai écrit un Gist tout à l'heure pour expliquer cela parce que la conversation est devenue trop longue pour Twitter.

L'utilisation pg.connectest la voie à suivre dans un environnement Web.

Le serveur PostgreSQL ne peut gérer qu'une seule requête à la fois par connexion. Cela signifie que si vous avez 1 global new pg.Client()connecté à votre backend, votre application entière est contrôlée en fonction de la vitesse à laquelle postgres peut répondre aux requêtes. Il alignera littéralement tout, mettant chaque requête en file d'attente. Oui, c'est asynchrone et donc ça va ... mais ne voudriez-vous pas plutôt multiplier votre débit par 10x? Utilisez pg.connect régler le pg.defaults.poolSizesur quelque chose de sain (nous faisons 25-100, pas encore sûr du bon nombre).

new pg.Clientest pour quand vous savez ce que vous faites. Lorsque vous avez besoin d'un seul client à long terme pour une raison quelconque ou que vous devez contrôler très soigneusement le cycle de vie. Un bon exemple de ceci est lors de l'utilisation LISTEN/NOTIFY. Le client qui écoute doit être présent et connecté et non partagé pour pouvoir gérer correctement les NOTIFYmessages. Un autre exemple serait lors de l'ouverture d'un client unique pour tuer des éléments bloqués ou dans des scripts de ligne de commande.

Une chose très utile est de centraliser tous les accès à votre base de données dans votre application dans un seul fichier. Ne gaspillez pas les pg.connectappels ou les nouveaux clients partout. Avoir un fichier comme db.jscelui-ci ressemble à ceci:

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 cette façon, vous pouvez changer votre implémentation pg.connecten un pool personnalisé de clients ou autre et ne devez changer les choses qu'en un seul endroit.

Jetez un œil au module node-pg-query qui fait exactement cela.


2
Désolé, je suis assez nouveau dans le SGBD et j'ai encore du mal à comprendre cela, mais pourquoi ne voulons-nous pas "jeter les appels pg.connect"? Est-ce pour des raisons de simplicité ou pour des raisons de performances? Par exemple, j'appelle pg.connect une fois dans chacune des routes que j'ai dans mon application de base (toutes avec le même conString). Est-ce correct? Intuitivement, j'ai l'impression de créer une nouvelle connexion avec la même base de données chaque fois que je l'appelle (ce que je ne veux pas), mais utilise-t-il les connexions regroupées en interne? Merci.
user1164937

Impressionnant. Pourquoi utilisez-vous une connexion par requête au lieu d'une par requête? J'ai cherché un moyen approprié de partager une connexion entre plusieurs requêtes dans une demande et j'avais envisagé res.locals avant de trouver votre réponse ici.
Joe Lapp

2
Oh, attendez. Il semble que votre solution ici ne prend pas en charge les transactions.
Joe Lapp

Cela devrait être lié au github.
Ryan Willis le

1
Notez que pg.connect a été supprimé après la v7 de node-postgres aka pg. Voir stackoverflow.com/questions/45174120/pg-connect-not-a-function
Colin D

23

Je suis l'auteur de pg-promise , qui simplifie l'utilisation de node-postgres via des promesses.

Il résout les problèmes liés à la bonne manière de se connecter et de se déconnecter de la base de données, en utilisant le pool de connexions implémenté par node-postgres , entre autres, comme les transactions automatisées.

Une demande individuelle dans pg-promise se résume à ce qui est pertinent pour votre logique métier:

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

c'est-à-dire que vous n'avez pas besoin de gérer la logique de connexion lors de l'exécution de requêtes, car vous ne configurez la connexion qu'une seule fois, globalement, comme ceci:

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;

Vous pouvez trouver de nombreux autres exemples dans le didacticiel Apprendre par l'exemple ou sur la page d'accueil du projet .


Bonjour, Heroku n'accepte que les connexions SSL. Dans pgceci est spécifié par pg.defaults.ssl = true;. Comment faites-vous cela pg-promise?
ocram

@ocram github.com/vitaly-t/pg-promise/wiki/… , ou vous pouvez spécifier SSL dans les paramètres de connexion: github.com/vitaly-t/pg-promise/wiki/Connection-Syntax
vitaly-t

Je suis nouveau dans la plupart de ces domaines: javascript, promesses, postgres, etc. et c'est exactement ce dont j'avais besoin. Je vous remercie!!
Ryan Rodemoyer

1
@ocram Je viens de faire travailler cela en faisantpgp.pg.defaults.ssl = true;
CharlieC

cela créera-t-il plusieurs connexions pour améliorer automatiquement le débit de postgres lorsque nous donnons plusieurs requêtes de requête à postgres?
dimanche

5

La piscine est la voie à suivre maintenant.

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)
      }

il peut être utilisé comme db.query('<BEGIN,COMMIT,ROLLBACK,your query,anything')


1

Il est préférable de créer un pool pg globalement et chaque fois que vous devez effectuer une opération de base de données, utilisez le client, puis relâchez-le dans le pool. Une fois toutes les opérations de base de données terminées, mettez fin au poolpool.end()

Exemple de code -

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();

        });
}

});  

Pour plus de détails, vous pouvez vous référer à mon article de blog - Source



-1

J'étais intéressé par un gestionnaire très simple pour cela, alors j'ai fait le mien sans le rendre trop compliqué. Je n'ai aucune illusion sur le fait que c'est super basique, mais cela pourrait aider certaines personnes à démarrer. Fondamentalement, il se connecte, exécute des requêtes et gère les erreurs pour vous.

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);
    });
  });
}

Ensuite, vous utiliseriez en l'appelant de cette façon:

runQuery("SELECT * FROM table", function(result) {
  // Whatever you need to do with 'result'
}

Cela ne libère même pas la connexion au pool. Cela épuisera la piscine très rapidement. L'exemple de base sur la node-postgrespage fait mieux que cela.
vitaly-t

-2

Voici comment je le fais, une sorte de "tout ce qui précède"

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()

1
Vous vous retrouverez donc sans gestion des connexions, sans prise en charge des transactions et sans prise en charge des tâches. Quel est le point alors?
vitaly-t

1
quelle langue est-ce? café? berk
caub
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.