@neondatabase/neon-js
v0.6.0-beta
Published
TypeScript SDK for Neon Auth and Data API - authentication and PostgreSQL querying
Readme
@neondatabase/neon-js
The official TypeScript SDK for Neon, combining authentication and database querying in a familiar interface.
Overview
@neondatabase/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 (Supabase-compatible, React hooks)
- 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 @neondatabase/neon-js
# or
bun add @neondatabase/neon-jsPrerequisites
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/v13. 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/auth4. 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/v1Quick Start
import { createClient } from '@neondatabase/neon-js';
// Database type generated via: npx @neondatabase/neon-js gen-types --db-url "..."
// 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:
SupabaseAuthAdapter (Supabase-compatible API)
Use this adapter if you're migrating from Supabase or prefer the Supabase API style:
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
// Database type generated via: npx @neondatabase/neon-js gen-types --db-url "..."
// See "TypeScript" section below for details
const client = createClient<Database>({
auth: {
adapter: SupabaseAuthAdapter(),
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();BetterAuthReactAdapter (React Hooks)
Use this adapter in React applications to get access to hooks like useSession:
import { createClient } from '@neondatabase/neon-js';
import { BetterAuthReactAdapter } from '@neondatabase/neon-js/auth/react/adapters';
// Database type generated via: npx @neondatabase/neon-js gen-types --db-url "..."
// 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 '@neondatabase/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();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();SupabaseAuthAdapter API
When using SupabaseAuthAdapter, 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 '@neondatabase/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/v1import { createClient } from '@neondatabase/neon-js';
const client = createClient({
auth: {
url: process.env.NEON_AUTH_URL!,
},
dataApi: {
url: process.env.NEON_DATA_API_URL!,
},
});UI Components
Pre-built login forms and auth pages live in @neondatabase/auth-ui. The
legacy @neondatabase/neon-js/auth/react/ui and @neondatabase/neon-js/ui/*
entrypoints still work for compatibility, but they are deprecated and will be
removed in the next major version.
To migrate existing imports, run:
npx -p @neondatabase/auth neon-auth-codemod --write <path>1. Import CSS
Install the UI package alongside @neondatabase/neon-js:
npm install @neondatabase/auth-uiWithout Tailwind CSS:
import '@neondatabase/auth-ui/css';With Tailwind CSS v4:
@import 'tailwindcss';
@import '@neondatabase/auth-ui/tailwind';2. Setup Provider
"use client"
import { NeonAuthUIProvider } from "@neondatabase/auth-ui"
import "@neondatabase/auth-ui/css"
export function Providers({ children }) {
return (
<NeonAuthUIProvider authClient={client.auth} redirectTo="/dashboard">
{children}
</NeonAuthUIProvider>
)
}3. Use Components
Option A: Full Auth Pages (Recommended)
Use AuthView to render complete auth flows based on the URL path:
import { AuthView } from "@neondatabase/auth-ui"
// Renders sign-in, sign-up, forgot-password, etc. based on path
<AuthView path="sign-in" />Option B: Individual Components
import { SignInForm, UserButton } from "@neondatabase/auth-ui"
<SignInForm />
<UserButton />Available components: SignInForm, SignUpForm, UserButton, AuthView, AccountView, OrganizationView
For full documentation and theming, see @neondatabase/auth-ui.
TypeScript
Generate TypeScript types from your database schema:
npx @neondatabase/neon-js gen-types --db-url "postgresql://user:pass@host/db"Use generated types for full type safety:
import type { Database } from './types/database';
import { createClient } from '@neondatabase/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 valuesCLI Tool
Generate TypeScript types from your database:
# Generate types
npx @neondatabase/neon-js gen-types \
--db-url "postgresql://user:pass@host/db" \
--output ./types/database.ts
# With schema filtering
npx @neondatabase/neon-js gen-types \
--db-url "postgresql://user:pass@host/db" \
--schemas public,auth \
--output ./types/database.tsOptions:
--db-url,-c- PostgreSQL connection string (required)--output,-o- Output file path (default:./types/database.ts)--schemas,-s- Comma-separated list of schemas (default:public)
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 14+ (with native fetch or polyfill)
- Browser (all modern browsers)
- Edge Runtime (Vercel, Cloudflare Workers, Deno, etc.)
- Bun (native support)
Error Handling
import { AuthError } from '@neondatabase/neon-js';
// Auth errors (SupabaseAuthAdapter)
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 '@neondatabase/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 '@neondatabase/neon-js';
import { BetterAuthReactAdapter } from '@neondatabase/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-js provides a Supabase-compatible API, making migration straightforward.
1. Update Dependencies
- "@supabase/supabase-js": "^2.74.0"
+ "@neondatabase/neon-js": "^0.1.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, SupabaseAuthAdapter } from '@neondatabase/neon-js';
- export const client = createClient(
- import.meta.env.VITE_SUPABASE_URL,
- import.meta.env.VITE_SUPABASE_ANON_KEY
- );
+ export const client = createClient<Database>({
+ auth: {
+ adapter: SupabaseAuthAdapter(), // Must call as function!
+ 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:
@neondatabase/auth- Authentication adapters (can be used standalone)@neondatabase/postgrest-js- PostgreSQL client (can be used standalone)
Resources
Support
License
Apache-2.0
