mrkeyoor.com_
Thu 29 Oct 22:47 UTC
npmWeb Backendupdated 29 Oct 2026

@types/pg review

You can give a PostgreSQL query the wrong row type and still satisfy these declarations. @types/pg 8.23.1 supplies TypeScript declarations for pg. Its query declarations accept SQL as a string and let the caller choose the result-row generic. That gives your editor a contract for Pool, Client, query configuration and returned rows. It leaves a consequential gap: a row type you write yourself does not prove that the SQL selects those columns or returns those values. Use the package to describe your database boundary, then decide where that boundary needs runtime checks.

Verdict

Add @types/pg when your TypeScript application uses pg and needs its contracts. Keep row types beside the SQL and validate uncertain output before trusting it; annotating the expected result cannot establish what the database returned.

Answers from our run

@types/pg or postgres: which should you use?

postgres: Consider postgres 4.0.0 when you are choosing a PostgreSQL driver and want one that declares its own types entry. Changing drivers needs a separate API and deployment evaluation. Add @types/pg when your TypeScript application uses pg and needs its contracts.

When should you not use @types/pg?

You expect the compiler to infer selected columns by reading SQL strings. These declarations accept strings and trust your chosen row generic.

API stability3/5On the stated rubric, the 8.23.1 declaration surface is usable but leaves material caveats. Pool and Client query overloads name row and input generics, while QueryResult exposes rows with that selected type. The default row type is any and SQL remains a string, so the contract cannot detect a mismatch between a selected column and its annotation. This rating describes the inspected API boundary, not a claim that upgrades are always compatible.
Docs3/5The published index.d.ts makes PoolConfig, QueryConfig, QueryArrayConfig and result types inspectable in one small file. Its Pool constructor comment explains that configuration reaches each client and links to the pool API. Many other declarations have no explanatory comments. Readers can find accepted shapes, but must supply the SQL and runtime context themselves; declarations alone are incomplete onboarding.
Maintenance3/5The pinned npm record identifies DefinitelyTyped's types/pg directory as the source and provides declaration export paths plus TypeScript version metadata. That makes ownership and package contents inspectable. Wildcard @types/node and pg-protocol dependencies make lockfile review necessary. This is a middle rating for traceability with dependency caveats; it does not infer pg-specific support quality from activity across the entire DefinitelyTyped repository.
Ecosystem4/5The dated npm response records 92,361,055 downloads in the stated October interval, which supports substantial distribution but cannot count unique users. The declarations connect to Node, pg-types and pg-protocol, as the pinned metadata shows. That earns a distribution-focused score above the middle anchor. It does not establish compatibility with every pg extension or make downloaded row annotations correct.

Use it if

  • Your TypeScript service already uses pg and needs declarations for its Pool, Client and query results.
  • A shared database helper should state its input tuple and returned row shape instead of passing untyped results to every caller.
  • You want the compiler to check configuration objects and distinguish object rows from rowMode: array results.
  • You will review row annotations alongside SQL and keep runtime checks where database output crosses a trust boundary.
Skip it if

Setup reality

Install pg as the runtime dependency and @types/pg as a development dependency in an application that compiles TypeScript before deployment. For example, npm install pg followed by npm install -D @types/pg@8.23.1. Import application values and types from 'pg', not from '@types/pg'. The inspected pg 8.23.0 metadata declares JavaScript entry points without a types entry; @types/pg 8.23.1 separately declares index.d.ts and import/require declaration exports. That is the package split, not a tested compatibility guarantee for every driver release.

The examples below were read against the published 8.23.1 declarations. We did not install the package, run a compiler, connect to PostgreSQL or execute these queries for this guide. Supply your application's pool where a function accepts one. Query literals use PostgreSQL casts so the expected string or boolean is visible in the SQL; changing a row generic still cannot change the returned value. The declaration metadata lists TypeScript 5.6. Check the compiler and module-resolution settings used by your project rather than treating that metadata as a test of your build.

The annoying part is maintaining two descriptions of one query: SQL and the row type you attach to it. Aliases, nullable columns, parser choices and query edits can make the descriptions disagree. An omitted row generic defaults to any in the inspected query overloads. An explicit tuple can constrain the values passed to a helper, but it cannot count placeholders inside arbitrary SQL strings. QueryResultBase declares rowCount as number | null. Handle that union instead of asserting that every command reports a count.

Pin the declaration package in your lockfile and review its dependency tree. The release depends on pg-types ^2.2.0, @types/node * and pg-protocol *. A pg major number and an @types/pg major number are not a complete compatibility policy. Compile your own query helpers during upgrades. For connection lifecycle and operational choices, read the existing pg driver guide.

npm recorded 92,361,055 downloads for @types/pg over 2026-10-22 through 2026-10-28. Those are package downloads, not unique developers or evidence that a particular query is safe. Scores below use an editorial 1-5 rubric: 1 means substantial obstacles, 3 means useful with material caveats, and 5 means strong inspected support with few caveats. Intermediate scores sit between those anchors. API stability here means clarity of the inspected contract, not a measured history of breaking changes; maintenance means dependency and ownership clarity, not a response-time audit.

Patterns

Check a pool configuration object type-pool-config

import type { PoolConfig } from 'pg';

export const config: PoolConfig = {
  application_name: 'billing-worker',
  max: 4,
  connectionTimeoutMillis: 3000,
};

Declaration-based illustration, unexecuted. PoolConfig extends ClientConfig. These values are examples, not sizing advice, and this object does not create a connection.

Describe the object row you expect describe-object-row

import type { Pool } from 'pg';

type LabelRow = { label: string };

export async function readLabel(pool: Pool) {
  const result = await pool.query<LabelRow>(
    "SELECT 'ready'::text AS label",
  );
  return result.rows[0]?.label;
}

Unexecuted declaration-based illustration. The row generic supplies label to TypeScript. The optional access also allows for a missing row; the generic itself does not establish row presence.

Recognize a believable but wrong annotation expose-generic-limit

import type { Pool } from 'pg';

export async function wrongContract(pool: Pool) {
  const result = await pool.query<{ label: number }>(
    "SELECT 'ready'::text AS label",
  );
  return result.rows[0]?.label;
}

Deliberately wrong row contract, not recommended application code. The declaration accepts a caller-selected numeric label while the SQL asks for text. This is a reading of the overload, not a recorded compiler or database result.

Check a value before returning it check-unknown-row

import type { Pool } from 'pg';

export async function checkedLabel(pool: Pool): Promise<string> {
  const result = await pool.query<{ label: unknown }>(
    "SELECT 'ready'::text AS label",
  );
  const row = result.rows[0];
  if (!row || typeof row.label !== 'string') {
    throw new Error('Expected one text label');
  }
  return row.label;
}

Unexecuted illustration. The explicit unknown forces a check before string use in this function. This guard checks one property and row presence, not an entire database schema.

State the parameter tuple separately type-input-tuple

import type { Pool } from 'pg';

type EchoRow = { label: string };

export async function echo(pool: Pool, label: string) {
  return pool.query<EchoRow, [string]>(
    'SELECT $1::text AS label',
    [label],
  );
}

Unexecuted illustration of both generics. QueryConfigValues accepts an array-shaped type, so [string] describes one string input. It does not relate $1 in the SQL text to that tuple.

Keep SQL and values in a typed config type-query-config

import type { QueryConfig } from 'pg';

export function labelQuery(label: string): QueryConfig<[string]> {
  return {
    text: 'SELECT $1::text AS label',
    values: [label],
  };
}

Unexecuted illustration. QueryConfig requires text and permits values with the supplied tuple type. It describes query input, not the output row shape.

Use the array-result overload deliberately describe-array-row

import type { Pool } from 'pg';

export async function readPair(pool: Pool) {
  const result = await pool.query<[string, boolean]>({
    text: "SELECT 'ready'::text, true::boolean",
    rowMode: 'array',
  });
  const row = result.rows[0];
  return row ? { label: row[0], active: row[1] } : undefined;
}

Unexecuted declaration-based illustration. QueryArrayConfig requires the literal array rowMode and selects QueryArrayResult. Column order is your responsibility; a tuple annotation does not inspect SQL positions.

Preserve the nullable command count handle-null-count

import type { QueryResultBase } from 'pg';

export function describeCount(result: QueryResultBase): string {
  return result.rowCount === null
    ? 'No row count reported'
    : `${result.rowCount} rows reported`;
}

Unexecuted type illustration. The declaration allows a null rowCount. Do not turn it into zero unless your application deliberately treats an absent count and a zero count as equivalent.

Accept a checked-out client explicitly preserve-client-boundary

import type { PoolClient, QueryResult } from 'pg';

type StateRow = { ready: boolean };

export async function readState(
  client: PoolClient,
): Promise<QueryResult<StateRow>> {
  return client.query<StateRow>('SELECT true::boolean AS ready');
}

Unexecuted illustration. PoolClient extends Client and adds release(err?: Error | boolean). This helper accepts the client without claiming ownership of its release or demonstrating transaction or concurrency behavior.

Keep a notification payload optional type-notification-payload

import type { Notification } from 'pg';

export function readNotice(message: Notification): string {
  return message.payload ?? '';
}

Unexecuted type illustration. Notification declares channel and processId, but payload is optional. Choosing an empty string here is an application policy, not evidence that every notification carries text.

Alternatives

PackageRegistryPick it when
postgresnpmConsider postgres 4.0.0 when you are choosing a PostgreSQL driver and want one that declares its own types entry. Changing drivers needs a separate API and deployment evaluation.
@neondatabase/serverlessnpmConsider @neondatabase/serverless 1.2.0 when evaluating a driver aimed at serverless environments; its package declares an index.d.ts entry. It is a driver choice, not a declarations-only replacement for an existing pg application.

More web backend guides

urllib3 · requests · ws · undici · anyio · httpx · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.