Skip to content

Latest commit

 

History

History
289 lines (200 loc) · 9.93 KB

README.md

File metadata and controls

289 lines (200 loc) · 9.93 KB

@neondatabase/serverless

@neondatabase/serverless is Neon's PostgreSQL driver for JavaScript and TypeScript. It's:

  • Low-latency, thanks to message pipelining and other optimizations
  • Ideal for serverless/edge deployment, using https and WebSockets in place of TCP
  • A drop-in replacement for node-postgres, aka pg (on which it's based)

Get started

Install it

Install it with your preferred JavaScript package manager. It's named @neondatabase/serverless on npm and @neon/serverless on JSR. So, for example:

npm install @neondatabase/serverless

or

bunx jsr add @neon/serverless

Using TypeScript? No worries: types are included either way.

Configure it

Get your connection string from the Neon console and set it as an environment variable. Something like:

DATABASE_URL=postgres://username:[email protected]/neondb

Use it

For one-shot queries, use the neon function. For instance:

import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL);

const [post] = await sql`SELECT * FROM posts WHERE id = ${postId}`;
// `post` is now { id: 12, title: 'My post', ... } (or undefined)

Note: interpolating ${postId} here is safe from SQL injection.

Deploy it

Turn this example into a complete API endpoint deployed on Vercel Edge Functions at https://myapp.vercel.dev/api/post?postId=123 by following two simple steps:

  1. Create a new file api/post.ts:
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL);

export default async (req: Request, ctx: any) => {
  // get and validate the `postId` query parameter
  const postId = parseInt(new URL(req.url).searchParams.get('postId'), 10);
  if (isNaN(postId)) return new Response('Bad request', { status: 400 });

  // query and validate the post
  const [post] = await sql`SELECT * FROM posts WHERE id = ${postId}`;
  if (!post) return new Response('Not found', { status: 404 });

  // return the post as JSON
  return new Response(JSON.stringify(post), {
    headers: { 'content-type': 'application/json' }
  });
}

export const config = {
  runtime: 'edge',
  regions: ['iad1'],  // specify the region nearest your Neon DB
};
  1. Test and deploy
npm install -g vercel  # install vercel CLI
npx vercel env add DATABASE_URL  # paste Neon connection string, select all environments
npx vercel dev  # check working locally, then ...
npx vercel deploy

The neon query function has a few additional options.

Sessions, transactions, and node-postgres compatibility

A query using the neon function, as shown above, is carried by an https fetch request.

This should work — and work fast — from any modern JavaScript environment. But you can only send one query at a time this way: sessions and transactions are not supported.

transaction()

Multiple queries can be issued via fetch request within a single, non-interactive transaction by using the transaction() function. This is exposed as a property on the query function.

For example:

import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL);
const showLatestN = 10;

const [posts, tags] = await sql.transaction([
  sql`SELECT * FROM posts ORDER BY posted_at DESC LIMIT ${showLatestN}`,
  sql`SELECT * FROM tags`,
]);

There are some additional options when using transaction().

Pool and Client

Use the Pool or Client constructors, instead of the functions described above, when you need:

  • session or interactive transaction support, and/or

  • compatibility with node-postgres, which supports query libraries like Kysely or Zapatos.

Queries using Pool and Client are carried by WebSockets. There are two key things to know about this:

  1. In Node.js and some other environments, there's no built-in WebSocket support. In these cases, supply a WebSocket constructor function.

  2. In serverless environments such as Vercel Edge Functions or Cloudflare Workers, WebSocket connections can't outlive a single request.

    That means Pool or Client objects must be connected, used and closed within a single request handler. Don't create them outside a request handler; don't create them in one handler and try to reuse them in another; and to avoid exhausting available connections, don't forget to close them.

These points are demonstrated in the examples below.

API

Example: Node.js with Pool.connect()

In Node.js, it takes two lines to configure WebSocket support. For example:

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

import ws from 'ws';
neonConfig.webSocketConstructor = ws; // <-- this is the key bit

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
pool.on('error', (err) => console.error(err)); // deal with e.g. re-connect
// ...

const client = await pool.connect();

try {
  await client.query('BEGIN');
  const {
    rows: [{ id: postId }],
  } = await client.query('INSERT INTO posts (title) VALUES ($1) RETURNING id', [
    'Welcome',
  ]);
  await client.query('INSERT INTO photos (post_id, url) VALUES ($1, $2)', [
    postId,
    's3.bucket/photo/url',
  ]);
  await client.query('COMMIT');
} catch (err) {
  await client.query('ROLLBACK');
  throw err;
} finally {
  client.release();
}

// ...
await pool.end();

Other WebSocket libraries are available. For example, you could replace ws in the above example with undici:

import { WebSocket } from 'undici';
neonConfig.webSocketConstructor = WebSocket;

Example: Vercel Edge Function with Pool.query()

We can rewrite the Vercel Edge Function shown above (under the heading 'Deploy it') to use Pool, as follows:

import { Pool } from '@neondatabase/serverless';

// *don't* create a `Pool` or `Client` here, outside the request handler

export default async (req: Request, ctx: any) => {
  // create a `Pool` inside the request handler
  const pool = new Pool({ connectionString: process.env.DATABASE_URL });

  try {
    // get and validate the `postId` query parameter
    const postId = parseInt(new URL(req.url).searchParams.get('postId'), 10);
    if (isNaN(postId)) return new Response('Bad request', { status: 400 });

    // query and validate the post
    const { rows: [post] } = await pool.query('SELECT * FROM posts WHERE id = $1', [postId]);
    if (!post) return new Response('Not found', { status: 404 });

    // return the post as JSON
    return new Response(JSON.stringify(post), {
      headers: { 'content-type': 'application/json' }
    });

  } finally {
    // end the `Pool` inside the same request handler
    // (unlike `await`, `ctx.waitUntil` won't hold up the response)
    ctx.waitUntil(pool.end());
  }
}

export const config = {
  runtime: 'edge',
  regions: ['iad1'],  // specify the region nearest your Neon DB
};

Note: we don't actually use the pooling capabilities of Pool in this example. But it's slightly briefer than using Client and, because Pool.query is designed for one-shot queries, we may in future automatically route these queries over https for lower latency.

Example: Vercel Edge Function with Client

Using Client instead, the example looks like this:

import { Client } from '@neondatabase/serverless';

// don't create a `Pool` or `Client` here, outside the request handler

export default async (req: Request, ctx: any) => {
  // create a `Client` inside the request handler
  const client = new Client(process.env.DATABASE_URL);
  await client.connect();

  try {
    // get and validate the `postId` query parameter
    const postId = parseInt(new URL(req.url).searchParams.get('postId'), 10);
    if (isNaN(postId)) return new Response('Bad request', { status: 400 });

    // query and validate the post
    const { rows: [post] } = await client.query('SELECT * FROM posts WHERE id = $1', [postId]);
    if (!post) return new Response('Not found', { status: 404 });

    // return the post as JSON
    return new Response(JSON.stringify(post), {
      headers: { 'content-type': 'application/json' }
    });

  } finally {
    // end the `Client` inside the same request handler
    // (unlike `await`, `ctx.waitUntil` won't hold up the response)
    ctx.waitUntil(client.end());
  }
}

export const config = {
  runtime: 'edge',
  regions: ['iad1'],  // specify the region nearest your Neon DB
};

More examples

These repos show how to use @neondatabase/serverless with a variety of environments and tools:

Bring your own Postgres database

This package comes configured to connect to a Neon database. But you can also use it to connect to your own Postgres instances if you run your own WebSocket proxy.

Open-source

This code is released under the MIT license.

Feedback and support

Please visit Neon Community or Support.