mrkeyoor.com_
Wed 23 Sept 00:36 UTC
npmDataupdated 22 Sept 2026

@neondatabase/serverless review

@neondatabase/serverless 1.1.0 is Neon's PostgreSQL driver for JavaScript runtimes that use HTTPS or WebSockets instead of a normal database TCP socket. Our browser bundle measured 140.8 KB minified and 44.9 KB gzipped. `neon()` sends one statement or a fixed transaction over fetch; `Pool` and `Client` use WebSockets for sessions and interactive transactions. Version 1.1 mainly repairs and generates TypeScript declarations across the API. Non-Neon PostgreSQL requires your own WebSocket proxy.

Verdict

@neondatabase/serverless 1.1.0 installed as 1 package and 1 MB in 0.9 seconds in our sandbox, but its browser bundle was 140.8 KB minified. Pick its HTTP path for independent Neon queries at the edge; use WebSockets only when session state or an interactive transaction earns the request-scoped lifecycle work.

We installed it

Lab card: what happened when we installed @neondatabase/serverlessScreenshot of @neondatabase/serverless documentation
Install✓ · 0.9s1 package on disk · 1 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser44.9 KBgzipped (140.8 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @neondatabase/serverless install cleanly?

Yes. In a fresh container with an empty cache, npm install @neondatabase/serverless finished in 0.9s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does @neondatabase/serverless add to a browser bundle?

44.9 KB gzipped (140.8 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does @neondatabase/serverless work with both ESM and CommonJS?

Yes. Both import '@neondatabase/serverless' and require('@neondatabase/serverless') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does @neondatabase/serverless include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@neondatabase/serverless or pg: which should you use?

pg: Use it for long-running Node services with direct TCP access and the standard node-postgres ecosystem. @neondatabase/serverless 1.1.0 installed as 1 package and 1 MB in 0.9 seconds in our sandbox, but its browser bundle was 140.8 KB minified.

When should you not use @neondatabase/serverless?

Production runs on Node 18 or earlier: package 1.1.0 declares node >=19.0.0

API stability4/5The project reached 1.0 in March 2025 and retains node-postgres-shaped `Pool` and `Client` classes beside the `neon()` HTTP API. Version 1.1 regenerated and corrected declarations without changing runtime behavior. The 1.0 line did raise the Node floor to 19 and made the HTTP query function tagged-template-only, moving text plus values to `sql.query()`. Those are understandable breaks, yet pre-1.0 code needs an explicit migration.
Docs5/5The README separates one-shot HTTP queries, fixed transactions, WebSocket sessions, and interactive transactions before giving Node and edge examples. `CONFIG.md` documents query composition, unsafe identifiers, array and full result modes, JWT headers, cancellation through fetch options, transaction isolation, and transport switches. Warnings cover request-scoped sockets, client-side credentials, and experimental or production-unsuitable TLS settings, so operational limits are visible before deployment.
Maintenance4/5Version 1.1.0 was published on 2026-04-17, and GitHub shows repository work as recently as 2026-08-19. The repository is active and not archived. GitHub currently counts 56 open issues and pull requests, which is a meaningful backlog across many runtimes and transports. The generated declaration overhaul and expanded tests in 1.1 are concrete maintenance work; advanced WebSocket configurations still deserve issue review before adoption.
Ecosystem4/5The npm API measured 3,613,784 downloads in the latest week. `Pool` and `Client` follow node-postgres conventions, letting Kysely, Zapatos, and other PostgreSQL tools reuse a familiar driver shape. ESM, CommonJS, declarations, Vercel examples, Cloudflare guidance, and JSR distribution cover many runtimes. HTTP transport remains tied to Neon, while another PostgreSQL host needs a proxy, so the integration breadth also creates vendor gravity.

Use it if

  • A Neon database is queried from an edge function, worker, or short-lived serverless handler without TCP access
  • Independent SQL statements fit the parameterized `neon()` tagged-template HTTP path
  • Interactive transactions or Kysely compatibility justify the WebSocket `Pool` or `Client` lifecycle
  • One package must expose CommonJS, ESM, generated TypeScript declarations, fetch queries, and node-postgres-style sessions
Skip it if

Setup reality

Our fresh install of @neondatabase/serverless 1.1.0 finished in 0.9 seconds and left 1 package using 1 MB on disk. It has no direct or peer dependencies, bundled TypeScript types, and 0 known audit vulnerabilities. The MIT package is 444 KB unpacked and requires Node 19 or newer.

Copy a Neon connection string into a server-side DATABASE_URL secret. The package is CommonJS with an exports map; both require() and ESM import worked in our Node 22 sandbox. Its browser bundle reached 140.8 KB minified and 44.9 KB gzipped. Direct browser use exposes credentials to untrusted clients, so put database access behind an authenticated server boundary even though bundling succeeds.

neon(DATABASE_URL) uses fetch and needs no WebSocket package. Since version 1, call the result as a tagged template; use sql.query(text, values) for stored SQL strings. sql.transaction() submits an array or a synchronous callback that returns an array. It cannot inspect one result before choosing the next statement. Pass an AbortSignal through fetchOptions when queries need a deadline, and clear the timer after completion.

Pool and Client switch to WebSockets for sessions and interactive transactions. Node 21 and earlier need a constructor such as ws assigned to neonConfig.webSocketConstructor. In edge handlers, create and close each client within the same request because its socket cannot survive that boundary. A non-Neon server also needs a separately deployed WebSocket proxy and transport settings. Cloudflare Worker users should compare Hyperdrive, which the project README specifically points out.

Patterns

Send one parameterized query run-http-query

import { neon } from '@neondatabase/serverless'

const sql = neon(process.env.DATABASE_URL)
const [post] = await sql`
  SELECT id, title FROM posts WHERE id = ${postId}
`

Interpolated values become parameters. The fetch request does not establish a reusable database session.

Insert and return the new record insert-returning-row

const [created] = await sql`
  INSERT INTO posts (title, author_id)
  VALUES (${title}, ${authorId})
  RETURNING id, title, author_id
`

Default HTTP results are row arrays. Enable full results when command and field metadata are also needed.

Reuse a safe WHERE fragment compose-sql-fragments

const filter = publishedOnly
  ? sql`WHERE published_at IS NOT NULL AND author_id = ${authorId}`
  : sql`WHERE author_id = ${authorId}`
const rows = await sql`SELECT id, title FROM posts ${filter}`

Version 1 compiles nested fragments lazily, preserving correct parameter positions.

Run stored SQL text with values execute-query-string

const text = 'SELECT id, title FROM posts WHERE author_id = $1'
const rows = await sql.query(text, [authorId])

Use `sql.query()` for strings. Calling `sql(text, values)` throws in the 1.x API.

Allowlist a dynamic column name use-dynamic-identifier

const allowed = new Set(['created_at', 'title'])
if (!allowed.has(sortColumn)) throw new Error('invalid sort column')
const rows = await sql`
  SELECT id, title FROM posts ORDER BY ${sql.unsafe(sortColumn)}
`

`sql.unsafe()` performs no escaping. Parameters cannot represent identifiers, so check against a closed list first.

Submit a fixed HTTP transaction run-fixed-transaction

const [accounts, audits] = await sql.transaction([
  sql`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${fromId} RETURNING balance`,
  sql`INSERT INTO audit_log (account_id, amount) VALUES (${fromId}, ${amount}) RETURNING id`,
])

Both statements are defined before submission. The second cannot depend on the first result.

Request serializable read-only work set-transaction-options

const results = await sql.transaction(queries, {
  isolationLevel: 'Serializable',
  readOnly: true,
  deferrable: true,
})

PostgreSQL gives `deferrable` meaning only for serializable, read-only transactions.

Receive command and field metadata return-full-result

const fullSql = neon(process.env.DATABASE_URL, { fullResults: true })
const result = await fullSql`SELECT id, title FROM posts`
console.log(result.rowCount, result.command, result.fields, result.rows)

`fullResults` changes the response from a rows array to a node-postgres-style result object.

Receive positional row arrays return-array-rows

const arraySql = neon(process.env.DATABASE_URL, { arrayMode: true })
const rows = await arraySql`SELECT id, title FROM posts ORDER BY id`
for (const [id, title] of rows) console.log(id, title)

Array mode drops property names, so keep the selected column order explicit.

Apply a five-second query deadline abort-http-query

const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), 5000)
try {
  return await sql.query(text, values, {
    fetchOptions: { signal: controller.signal },
  })
} finally {
  clearTimeout(timer)
}

HTTP cancellation travels through `fetchOptions`. Clear the timer on both successful and failed queries.

Use a WebSocket client for branching SQL run-interactive-transaction

import { Pool } from '@neondatabase/serverless'

const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const client = await pool.connect()
try {
  await client.query('BEGIN')
  const { rows: [account] } = await client.query(
    'SELECT balance FROM accounts WHERE id = $1 FOR UPDATE', [accountId]
  )
  if (account.balance < amount) throw new Error('insufficient funds')
  await client.query('COMMIT')
} catch (error) {
  await client.query('ROLLBACK')
  throw error
} finally {
  client.release()
  await pool.end()
}

`Pool` uses WebSockets. Inside serverless code, connect and close it within the same handler.

Supply WebSockets on Node 21 and earlier configure-websocket

import { Pool, neonConfig } from '@neondatabase/serverless'
import ws from 'ws'

neonConfig.webSocketConstructor = ws
const pool = new Pool({ connectionString: process.env.DATABASE_URL })

The README requires this constructor for older Node versions using `Pool` or `Client`; `neon()` over HTTP does not need it.

Alternatives

PackageRegistryPick it when
pgnpmUse it for long-running Node services with direct TCP access and the standard node-postgres ecosystem
postgresnpmUse it for tagged SQL in Node, Bun, or Deno when a conventional PostgreSQL connection is available
kyselynpmUse it when typed query construction is the missing layer and you are willing to choose a separate dialect driver

More data guides

numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · 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.