PostgreSQL Type Converters
pg-types is a core utility package designed to handle query result type conversions specifically for `node-postgres`, the popular PostgreSQL client for Node.js. It addresses the fundamental challenge of reconciling PostgreSQL's rich type system with JavaScript's more limited native types. By default, PostgreSQL servers return all data as strings. `node-postgres` then converts these into appropriate JavaScript types like `Date` or `Number` where possible. `pg-types` allows developers to override these default parsing behaviors for specific PostgreSQL OIDs (Object IDs). This is crucial for handling types like 64-bit integers (`int8`), which JavaScript cannot represent precisely without losing precision, or for integrating with custom data types. The current stable version is 4.1.0, with releases typically synchronized with its parent `node-postgres` project. Its key differentiator is the fine-grained control it offers over type parsing, enabling precise data handling and preventing potential data loss or incorrect interpretations, particularly with large numbers or complex date/time types. The package also ships with TypeScript definitions, enhancing developer experience for type-safe applications.
Common errors
-
Type 'string | undefined' is not assignable to type 'number | undefined'.
cause Environment variables (e.g., `process.env.PGPORT`) are always strings or undefined. TypeScript requires explicit conversion for numeric configuration properties.fixConvert environment variables to numbers using `parseInt()` or `Number()`: `port: parseInt(process.env.PGPORT ?? '5432', 10),`. Always provide a fallback default value. -
My `COUNT(*)` or `BIGINT` columns are returned as strings, not numbers.
cause `node-postgres` (and `pg-types`) defaults to returning large integers (`int8`) as strings to prevent precision loss, as JavaScript numbers cannot safely represent all 64-bit integers.fixIf you are certain the values will not exceed `Number.MAX_SAFE_INTEGER`, you can override the type parser: `types.setTypeParser(20, val => val === null ? null : parseInt(val, 10));`. For larger numbers, consider `BigInt(val)` or a dedicated big-number library. -
Custom array types or ENUMs are returned as raw strings (e.g., '{value1,value2}').cause `node-postgres` does not have built-in parsers for all custom PostgreSQL types or array representations. It defaults to returning these as their raw string representation.fixIdentify the OID of your custom type (e.g., `SELECT typname, oid FROM pg_type WHERE typname = 'my_type';`) and set a custom type parser using `types.setTypeParser(YOUR_OID, parseFunction)`. For array types, you might need to use `pg.types.getTypeParser` from a known array type (e.g., `OID_ARRAY_TEXT`) as a base.
Warnings
- breaking Converting PostgreSQL BIGINT (int8) or NUMERIC types directly to JavaScript's `number` type can lead to precision loss for values exceeding `Number.MAX_SAFE_INTEGER` (2^53 - 1). `node-postgres` defaults to returning these as strings to prevent silent data corruption.
- gotcha PostgreSQL's `TIMESTAMP` (without time zone) and `TIMESTAMPTZ` (with time zone) types are handled differently. `node-postgres` typically converts `TIMESTAMPTZ` values into JavaScript `Date` objects, which are inherently UTC, but `TIMESTAMP` might be interpreted based on the client's local timezone unless explicitly handled, potentially leading to timezone-related discrepancies. Additionally, JavaScript `Date` objects only support millisecond precision, truncating any microsecond precision from PostgreSQL timestamps.
- gotcha `pg-types` does not maintain a public `CHANGELOG.md` file, which makes tracking specific breaking changes or new features between minor and major versions challenging.
- breaking Upcoming `pg-types` 5.x (part of `node-postgres` 9.0) may introduce subtle breaking changes related to 'timezoneless date parsing' defaults. This could alter how `TIMESTAMP` (without time zone) values are interpreted, which might not align with previous versions' behavior and could be difficult to detect.
Install
-
npm install pg-types -
yarn add pg-types -
pnpm add pg-types
Imports
- types
import * as types from 'pg-types'; // While possible, typically accessed via 'pg' module.
import { types } from 'pg'; - types
const types = require('pg-types'); // Direct require is less common for runtime modification.const { types } = require('pg'); - TypeParser
import { TypeParser } from 'pg-types'; // Incorrect for type-only importimport type { TypeParser } from 'pg-types';
Quickstart
import { Client, types } from 'pg';
import moment from 'moment'; // Example dependency for custom date parsing
async function runPgTypeParsersExample() {
// PostgreSQL OID for BIGINT (int8) is 20.
// By default, node-postgres returns int8 as string to prevent overflow.
// Override to parse as JavaScript Number, *only if* confident values won't exceed Number.MAX_SAFE_INTEGER.
types.setTypeParser(20, (val: string) => {
// null values are never parsed by default.
return val === null ? null : parseInt(val, 10);
});
// PostgreSQL OID for TIMESTAMPTZ (timestamp with time zone) is 1184.
// Override to parse as moment objects for custom date handling.
// Requires 'moment' to be installed (npm install moment).
types.setTypeParser(types.builtins.TIMESTAMPTZ, (val: string) => {
return val === null ? null : moment(val);
});
// Example usage with a pg client
const client = new Client({
user: process.env.PGUSER ?? 'postgres',
host: process.env.PGHOST ?? 'localhost',
database: process.env.PGDATABASE ?? 'testdb',
password: process.env.PGPASSWORD ?? 'password',
port: parseInt(process.env.PGPORT ?? '5432', 10),
});
try {
await client.connect();
console.log('Connected to PostgreSQL.');
// Create a temporary table with relevant types
await client.query(`
CREATE TEMP TABLE IF NOT EXISTS my_types_test (
id SERIAL PRIMARY KEY,
big_int_col BIGINT,
timestamp_tz_col TIMESTAMPTZ
);
`);
console.log('Temporary table created.');
// Insert data (BIGINT as string, Date object for timestamp)
const insertResult = await client.query(
'INSERT INTO my_types_test(big_int_col, timestamp_tz_col) VALUES ($1, $2) RETURNING *',
['12345', new Date()]
);
console.log('Inserted row:', insertResult.rows[0]);
// Query data to see custom parsing in action
const selectResult = await client.query('SELECT * FROM my_types_test');
const parsedRow = selectResult.rows[0];
console.log('\nQueried row with custom parsers:');
console.log(`- big_int_col (parsed as Number): ${parsedRow.big_int_col} (Type: ${typeof parsedRow.big_int_col})`);
console.log(`- timestamp_tz_col (parsed as Moment): ${parsedRow.timestamp_tz_col} (Type: ${moment.isMoment(parsedRow.timestamp_tz_col) ? 'Moment' : typeof parsedRow.timestamp_tz_col})`);
} catch (err) {
console.error('Error during example execution:', err);
} finally {
await client.end();
console.log('Client disconnected.');
}
}
runPgTypeParsersExample();