node-postgres

repository·master·Indexed 11 days ago

https://github.com/brianc/node-postgres

A non-blocking PostgreSQL client for Node.js and other JavaScript runtimes. It provides a pure JavaScript implementation and an optional native libpq binding layer via pg-native. The ecosystem includes pg-cursor for result cursors, pg-connection-string for parsing connection strings, and pg-cloudflare for compatibility with Cloudflare Workers TCP Socket API.

Tokens
45.3K
Snippets
170
Records
202
Agent score
95%

What's inside node-postgres

  1. Overview of node-postgres features

    master

    node-postgres is a non-blocking PostgreSQL client for Node.js (and compatible runtimes like Bun, Deno, and Cloudflare). It provides a pure JavaScript implementation with optional native libpq bindings that share the same API.

    Key features include:

    • Connection pooling via pg-pool.
    • Extensible data-type coercion between JavaScript and PostgreSQL.
    • Parameterized queries for security.
    • Named statements with query plan caching.
    • Async notifications using LISTEN/NOTIFY.
    • Bulk import/export using COPY TO/COPY FROM via pg-query-stream.
  2. What is query pipelining?

    master
    By default, node-postgres waits for each query to complete before sending the next one, incurring a network round-trip for every query. Query pipelining allows you to send multiple queries to the server without waiting for responses. The server processes them in order, and each query receives its own result or error. This reduces idle time and can deliver 2-3x throughput for batches of simple queries, especially on high-latency links.
  3. Error isolation in pipelined queries

    master

    Each pipelined query maintains its own error boundary. If one query in a batch fails (e.g., due to a syntax error), it does not prevent the other queries in the same batch from completing. You can use Promise.allSettled to handle these results effectively.

    const results = await Promise.allSettled([
      client.query('SELECT 1 AS num'),
      client.query('SELECT INVALID SYNTAX'),
      client.query('SELECT 3 AS num'),
    ])
    
    console.log(results[0].status) // 'fulfilled'
    console.log(results[1].status) // 'rejected'
    console.log(results[2].status) // 'fulfilled'
  4. Related modules in the node-postgres monorepo

    master

    The node-postgres repository is a monorepo containing the core client and several specialized modules:

    • pg: The core PostgreSQL client.
    • pg-pool: Connection pooling logic.
    • pg-native: Native libpq bindings.
    • pg-cursor: Cursor support for iterating over large result sets.
    • pg-query-stream: Streaming query results.
    • pg-connection-string: Parsing PostgreSQL connection strings.
    • pg-protocol: The underlying PostgreSQL wire protocol implementation.
  5. How to execute transactions in node-postgres

    master

    node-postgres does not provide high-level transaction abstractions. Instead, you execute standard PostgreSQL transaction commands (BEGIN, COMMIT, and ROLLBACK) manually using a single client instance.

    Critical Requirement: Use a single client

    To ensure transaction isolation works correctly, you must use the same client instance for every statement within the transaction.

    Warning: Do not use pool.query for transactions. pool.query picks a random client from the pool for each call, which will cause the transaction to fail or behave unpredictably because PostgreSQL isolates transactions to specific clients.

    import { Pool } from 'pg'
    const pool = new Pool()
    
    const client = await pool.connect()
    
    try {
      await client.query('BEGIN')
      // ... execute queries ...
      await client.query('COMMIT')
    } catch (e) {
      await client.query('ROLLBACK')
      throw e
    } finally {
      client.release()
    }
  6. Use connection pooling with Pool

    master

    For most production applications, it is recommended to use a Pool instead of a single Client. A Pool manages a collection of connections, allowing you to execute queries without manually managing the connection lifecycle for every request.

    import { Pool } from 'pg'
    const pool = new Pool()
    const res = await pool.query('SELECT $1::text as message', ['Hello world!'])
    console.log(res.rows[0].message) // Hello world!
  7. Node.js version support and compatibility

    master

    node-postgres officially supports Node.js versions that are currently under LTS lifetime.

    While the library aims to avoid breaking changes, support for Node versions outside of the LTS lifetime may be dropped at any time to support new features or bug fixes.

    Recommendation for legacy environments: If you are running an older version of Node.js that is nearing End-of-Life (EOL), it is highly recommended to use a lockfile and pin the versions of all your modules, including node-postgres, to ensure stability.

  8. Understand the pg.Result object

    master

    Every successful query in node-postgres returns a pg.Result object. Note that you cannot instantiate this object directly; it is provided by the driver as the output of a query execution.

    The object contains the data returned by the database, metadata about the columns, the type of command executed, and the number of rows affected.

  9. How node-postgres handles data types

    master

    node-postgres automatically maps common PostgreSQL data types to JavaScript types. If a registered type parser is not available for a specific database type, node-postgres defaults to returning the value as a JavaScript string. You can also bypass parsing entirely by casting columns to text within your SQL query.

    To ensure you receive unparsed string values, use the ::text cast in your SQL:

    const queryText = 'SELECT int_col::text, date_col::text, json_col::text FROM my_table'
    const result = await client.query(queryText)
    
    console.log(result.rows[0]) // contains the unparsed string value of each column
    const queryText = 'SELECT int_col::text, date_col::text, json_col::text FROM my_table'
    const result = await client.query(queryText)
    
    console.log(result.rows[0])
  10. Best practice: Use a long-lived Pool instance

    master

    A Pool should be a long-lived object in your application. You should instantiate one pool when your app starts and reuse that same instance throughout the application's lifetime.

    Avoid creating a new pool instance inside a function that is called frequently (like a connect wrapper), as this will create an unbounded number of pools and connections.

    // CORRECT: Create the pool once and export its methods
    const pool = new pg.Pool()
    
    module.exports.query = (text, values) => {
      return pool.query(text, values)
    }
    
    // WRONG: Creating a new pool every time connect is called
    module.exports.connect = () => {
      const aPool = new pg.Pool()
      return aPool.connect()
    }
  11. Why use connection pooling with node-postgres

    master

    Connection pooling is highly recommended for web applications or software making frequent queries. Using a pool via the pg-pool module provides several benefits:

    • Performance: Avoids the 20-30ms handshake cost (password negotiation, SSL, configuration) required for every new connection.
    • Server Stability: Prevents crashing the PostgreSQL server by limiting the number of concurrent clients.
    • Concurrency: Allows multiple queries to run in parallel across different clients, rather than being serialized on a single client.

    In most applications, you should create a single Pool instance and reuse it.

  12. Parameter conversion rules

    master

    When passing parameters to query(), values are converted to raw data types using these rules:

    • null and undefined: Both are converted to null.
    • Date: Converted to a UTC date string.
    • Buffer: Remains unchanged.
    • Array: Converted to a string describing a Postgres array (items are recursively converted).
    • Object: If the object has a toPostgres method, it is called. The signature is toPostgres(prepareValue: (value) => any): any. If no such method exists, JSON.stringify is used.
    • Everything else: Converted via value.toString().