npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@neon/neon-js

v0.10.2

Published

TypeScript SDK for Neon Auth and Data API - authentication and PostgreSQL querying

Readme

@neon/neon-js

npm version npm downloads TypeScript License

The official TypeScript SDK for Neon, combining authentication and database querying in a familiar interface.

Overview

@neon/neon-js is a comprehensive SDK that brings together Neon Auth and Neon Data API. It provides a unified client for managing authentication and querying PostgreSQL databases with a familiar, intuitive interface.

Key Features:

  • Integrated Authentication - Works out of the box with optional adapters (React hooks) and a Supabase-compatible client at @neon/neon-js/supabase
  • PostgreSQL Querying - Full PostgREST client with type-safe queries
  • Anonymous Access - Optional RLS-based data access for unauthenticated users
  • High Performance - Session caching, request deduplication
  • Automatic Token Management - Seamless token injection for database queries
  • TypeScript First - Fully typed with strict type checking
  • Universal - Works in Node.js, browsers, and edge runtimes

How to query your Neon Serverless Postgres database?

It's up to you how you want to query your Postgres database! Neon Data API is a PostgREST-compatible REST service for your Neon database. neon-js exposes the REST-based query functions to leverage Data API. However, you can also use Data API directly with PostgREST and we also maintain a simple Postgres client here.

That said, you don't need Data API or neon-js to query Neon. Neon works great with all major ORMs (Drizzle, Prisma, Kysely) and Postgres clients (node-postgres, postgres.js). We also offer a serverless driver for edge and serverless environments without TCP client or connection pooling support (which also is available as a Drizzle and Prisma adapter).

Installation

npm install @neon/neon-js
# or
bun add @neon/neon-js

Prerequisites

Before using neon-js, you'll need:

1. A Neon Account and Project

  • Sign up at neon.tech
  • Create a new project in the Neon Console

2. Enable the Data API (for database queries)

  • Go to your project settings in Neon Console
  • Enable the Data API feature
  • Copy your Data API URL

Data API URL format:

https://ep-xxx.apirest.c-2.us-east-2.aws.neon.build/dbname/rest/v1

3. Enable Neon Auth (for authentication)

  • Go to your project settings in Neon Console
  • Enable Neon Auth
  • Copy your Auth URL

Auth URL format:

https://ep-xxx.neonauth.c-2.us-east-2.aws.neon.build/dbname/auth

4. Configure Environment Variables

Create a .env or .env.local file:

# Next.js
NEON_AUTH_BASE_URL=https://ep-xxx.neonauth.c-2.us-east-2.aws.neon.build/dbname/auth
NEXT_PUBLIC_NEON_AUTH_URL=https://ep-xxx.neonauth.c-2.us-east-2.aws.neon.build/dbname/auth
NEON_DATA_API_URL=https://ep-xxx.apirest.c-2.us-east-2.aws.neon.build/dbname/rest/v1

# Vite/React
VITE_NEON_AUTH_URL=https://ep-xxx.neonauth.c-2.us-east-2.aws.neon.build/dbname/auth
VITE_NEON_DATA_API_URL=https://ep-xxx.apirest.c-2.us-east-2.aws.neon.build/dbname/rest/v1

Quick Start

import { createClient } from '@neon/neon-js';

// `Database` is a type generated from your schema
// See "TypeScript" section below for details
const client = createClient<Database>({
  auth: {
    url: import.meta.env.VITE_NEON_AUTH_URL,
    // Optional: allow unauthenticated users to query data via RLS
    // allowAnonymous: true,
  },
  dataApi: {
    url: import.meta.env.VITE_NEON_DATA_API_URL,
  },
});

// Authenticate
await client.auth.signIn.email({
  email: '[email protected]',
  password: 'secure-password',
});

// Query database (token automatically injected)
const { data: users } = await client
  .from('users')
  .select('*')
  .eq('status', 'active');

Using Adapters

You can optionally specify an adapter for different API styles:

Supabase-compatible API

If you're migrating from Supabase or prefer the Supabase API style, import createClient from the /supabase subpath. It has the Supabase adapter built in, so there is no adapter option:

import { createClient } from '@neon/neon-js/supabase';

// `Database` is a type generated from your schema
// See "TypeScript" section below for details
const client = createClient<Database>({
  auth: {
    url: import.meta.env.VITE_NEON_AUTH_URL,
  },
  dataApi: {
    url: import.meta.env.VITE_NEON_DATA_API_URL,
  },
});

// Supabase-compatible API
await client.auth.signInWithPassword({
  email: '[email protected]',
  password: 'secure-password',
});

const { data: session } = await client.auth.getSession();

Auth config also accepts the SupabaseAuthAdapter options (fetchOptions, sessionOptions, ...). For example, a server can forward the incoming request cookie:

const client = createClient<Database>({
  auth: {
    url: process.env.NEON_AUTH_URL!,
    fetchOptions: { headers: { Cookie: request.headers.get('cookie') ?? '' } },
  },
  dataApi: { url: process.env.NEON_DATA_API_URL! },
});

The main createClient no longer accepts SupabaseAuthAdapter. If you already import it, switch to @neon/neon-js/supabase and drop the adapter option. SupabaseAuthAdapter itself is still exported from @neon/neon-js/auth/vanilla/adapters.

BetterAuthReactAdapter (React Hooks)

Use this adapter in React applications to get access to hooks like useSession:

import { createClient } from '@neon/neon-js';
import { BetterAuthReactAdapter } from '@neon/neon-js/auth/react/adapters';

// `Database` is a type generated from your schema
// See "TypeScript" section below for details
const client = createClient<Database>({
  auth: {
    adapter: BetterAuthReactAdapter(),
    url: import.meta.env.VITE_NEON_AUTH_URL,
  },
  dataApi: {
    url: import.meta.env.VITE_NEON_DATA_API_URL,
  },
});

// Use in React components
function MyComponent() {
  const session = client.auth.useSession();

  if (session.isPending) return <div>Loading...</div>;
  if (!session.data) return <div>Not logged in</div>;

  return <div>Hello, {session.data.user.name}</div>;
}

Anonymous Access

Enable allowAnonymous to let unauthenticated users query data. This uses an anonymous token for RLS-based access control:

import { createClient } from '@neon/neon-js';

const client = createClient<Database>({
  auth: {
    url: import.meta.env.VITE_NEON_AUTH_URL,
    allowAnonymous: true, // Enable anonymous data access
  },
  dataApi: {
    url: import.meta.env.VITE_NEON_DATA_API_URL,
  },
});

// Works without signing in - uses anonymous token for RLS
const { data: publicItems } = await client.from('public_items').select();

External Auth Provider

If your app authenticates with its own identity provider (Clerk, Auth0, and others) instead of Neon Auth, omit the auth block and pass a getToken function on dataApi. It is called lazily on every request and its JWT is attached to each Data API call — Neon validates it against the JWKS URL you configured for the provider.

import { createClient } from '@neon/neon-js';

const neon = createClient<Database>({
  dataApi: {
    url: import.meta.env.VITE_NEON_DATA_API_URL,
    // Return a valid JWT from your provider, e.g. Clerk's getToken()
    // or Auth0's getAccessTokenSilently().
    getToken: async () => await yourAuthProvider.getToken(),
  },
});

// Every request carries your provider's token, so RLS policies apply
const { data: items } = await neon.from('items').select();

This form returns a NeonPostgrestClient — the same query API as above, without the .auth namespace (auth is handled by your provider). No @neon/postgrest-js import is needed.

Authentication

Sign Up

await client.auth.signUp.email({
  email: '[email protected]',
  password: 'secure-password',
  name: 'John Doe',
});

Sign In

// Email & Password
await client.auth.signIn.email({
  email: '[email protected]',
  password: 'secure-password',
});

// OAuth
await client.auth.signIn.social({
  provider: 'google',
  callbackURL: '/dashboard',
});

Session Management

// Get current session
const session = await client.auth.getSession();

// Sign out
await client.auth.signOut();

Supabase-compatible API

When using @neon/neon-js/supabase, you get access to the Supabase-compatible API:

// Sign up with metadata
await client.auth.signUp({
  email: '[email protected]',
  password: 'secure-password',
  options: {
    data: { name: 'John Doe' },
  },
});

// Sign in
await client.auth.signInWithPassword({
  email: '[email protected]',
  password: 'secure-password',
});

// OAuth
await client.auth.signInWithOAuth({
  provider: 'google',
  options: { redirectTo: '/dashboard' },
});

// Session with data wrapper
const { data: session } = await client.auth.getSession();
const { data: user } = await client.auth.getUser();

// Auth state changes
client.auth.onAuthStateChange((event, session) => {
  console.log(event, session);
});

Database Querying

SELECT Queries

// Simple select
const { data } = await client
  .from('users')
  .select('id, name, email');

// With filters
const { data } = await client
  .from('posts')
  .select('*')
  .eq('status', 'published')
  .gt('views', 100)
  .order('created_at', { ascending: false })
  .limit(10);

// Joins
const { data } = await client
  .from('posts')
  .select(`
    id,
    title,
    author:users(name, email)
  `)
  .eq('status', 'published');

INSERT Queries

// Insert single row
const { data } = await client
  .from('users')
  .insert({
    name: 'Alice',
    email: '[email protected]',
  })
  .select();

// Insert multiple rows
const { data } = await client
  .from('users')
  .insert([
    { name: 'Bob', email: '[email protected]' },
    { name: 'Carol', email: '[email protected]' },
  ])
  .select();

UPDATE Queries

const { data } = await client
  .from('users')
  .update({ status: 'inactive' })
  .eq('last_login', null)
  .select();

DELETE Queries

const { data } = await client
  .from('users')
  .delete()
  .eq('status', 'deleted')
  .select();

RPC (Stored Procedures)

const { data } = await client
  .rpc('get_user_stats', {
    user_id: 123,
    start_date: '2024-01-01',
  });

Configuration

Client Options

import { createClient } from '@neon/neon-js';

const client = createClient({
  // Auth configuration
  auth: {
    url: 'https://your-auth-server.neon.tech/auth',
    allowAnonymous: true, // Optional: enable anonymous data access
  },

  // Data API configuration
  dataApi: {
    url: 'https://your-data-api.neon.tech/rest/v1',
    options: {
      db: {
        schema: 'public', // Default schema
      },
      global: {
        headers: {
          'X-Custom-Header': 'value',
        },
      },
    },
  },
});

Environment Variables

# Auth URL
NEON_AUTH_URL=https://your-auth-server.neon.tech/auth

# Data API URL
NEON_DATA_API_URL=https://your-data-api.neon.tech/rest/v1
import { createClient } from '@neon/neon-js';

const client = createClient({
  auth: {
    url: process.env.NEON_AUTH_URL!,
  },
  dataApi: {
    url: process.env.NEON_DATA_API_URL!,
  },
});

TypeScript

Pass a Database type generated from your schema to createClient for full type safety:

import type { Database } from './types/database';
import { createClient } from '@neon/neon-js';

const client = createClient<Database>({
  auth: {
    url: process.env.NEON_AUTH_URL!,
  },
  dataApi: {
    url: process.env.NEON_DATA_API_URL!,
  },
});

// Fully typed queries!
const { data } = await client
  .from('users') // Autocomplete for table names
  .select('id, name, email') // Autocomplete for column names
  .eq('status', 'active'); // Type checking for values

Performance

Session Caching

Sessions are cached in memory with intelligent TTL:

  • Cold start: ~200ms (single network request)
  • Cached reads: <1ms (in-memory, no I/O)
  • Cache TTL: 60 seconds or until JWT expires
  • Smart expiration: Automatic based on JWT claims

Request Deduplication

Concurrent authentication calls are automatically deduplicated:

  • Without deduplication: 10 concurrent calls = 10 requests (~2000ms)
  • With deduplication: 10 concurrent calls = 1 request (~200ms)
  • Result: 10x faster, N-1 fewer server requests

Environment Compatibility

  • Node.js 20.19+
  • Browser (all modern browsers)
  • Edge Runtime (Vercel, Cloudflare Workers, Deno, etc.)
  • Bun (native support)

Error Handling

import { AuthError } from '@neon/neon-js/auth';

// Auth errors (Supabase-compatible client)
try {
  await client.auth.signInWithPassword({ email, password });
} catch (error) {
  if (error instanceof AuthError) {
    console.error('Auth error:', error.message);
  }
}

// Database errors
const { data, error } = await client.from('users').select();
if (error) {
  console.error('Database error:', error.message);
}

Examples

Next.js App Router

// app/lib/neon.ts
import { createClient } from '@neon/neon-js';

export const neon = createClient({
  auth: {
    url: process.env.NEON_AUTH_URL!,
  },
  dataApi: {
    url: process.env.NEON_DATA_API_URL!,
  },
});

// app/api/users/route.ts
import { neon } from '@/lib/neon';

export async function GET() {
  const { data: users } = await neon.from('users').select('*');
  return Response.json(users);
}

React Hook with BetterAuthReactAdapter

import { createClient } from '@neon/neon-js';
import { BetterAuthReactAdapter } from '@neon/neon-js/auth/react/adapters';

const client = createClient({
  auth: {
    adapter: BetterAuthReactAdapter(),
    url: process.env.NEXT_PUBLIC_NEON_AUTH_URL!,
  },
  dataApi: {
    url: process.env.NEXT_PUBLIC_NEON_DATA_API_URL!,
  },
});

export function useAuth() {
  const session = client.auth.useSession();

  return {
    user: session.data?.user ?? null,
    isPending: session.isPending,
    signIn: (email: string, password: string) =>
      client.auth.signIn.email({ email, password }),
    signOut: () => client.auth.signOut(),
  };
}

Supabase Migration Guide

@neon/neon-js/supabase provides a Supabase-compatible API, making migration straightforward.

1. Update Dependencies

- "@supabase/supabase-js": "^2.74.0"
+ "@neon/neon-js": "^0.8.0"

2. Update Environment Variables

- VITE_SUPABASE_URL="https://xxx.supabase.co"
- VITE_SUPABASE_ANON_KEY="..."
+ VITE_NEON_DATA_API_URL="https://xxx.neon.tech/neondb/rest/v1"
+ VITE_NEON_AUTH_URL="https://your-auth-server.com"

3. Update Client Initialization

- import { createClient } from '@supabase/supabase-js';
+ import { createClient } from '@neon/neon-js/supabase';

- export const client = createClient(
-   import.meta.env.VITE_SUPABASE_URL,
-   import.meta.env.VITE_SUPABASE_ANON_KEY
- );
+ export const client = createClient<Database>({
+   auth: {
+     url: import.meta.env.VITE_NEON_AUTH_URL,
+   },
+   dataApi: {
+     url: import.meta.env.VITE_NEON_DATA_API_URL,
+   },
+ });

4. No Code Changes Needed

All authentication methods work the same:

// These work identically
await client.auth.signInWithPassword({ email, password });
await client.auth.signUp({ email, password });
const { data: session } = await client.auth.getSession();
client.auth.onAuthStateChange((event, session) => { /* ... */ });

All database queries work the same:

// These work identically
const { data } = await client.from('items').select();
await client.from('items').insert({ name: 'New Item' });
await client.from('items').update({ status: 'done' }).eq('id', 1);
await client.from('items').delete().eq('id', 1);

See the todo-guardian-pro migration PR for a complete migration example.

Related Packages

This package combines two underlying packages:

Resources

Support

License

Apache-2.0