| name | neon-js-react |
| description | Sets up the full Neon SDK with authentication AND database queries in React apps (Vite, CRA). Creates typed client, generates database types, and configures auth UI. Use for auth + database integration. |
| allowed-tools | ["Bash","Write","Read","Edit","Glob","Grep"] |
Neon JS for React
Help developers set up @neondatabase/neon-js with authentication AND database queries in React applications (Vite, CRA, etc.).
When to Use
Use this skill when:
- Setting up Neon Auth + Database in a React app (Vite, CRA, etc.)
- User needs both authentication AND database queries
- User mentions "neon-js", "neon auth + database", or "full neon SDK"
- User is NOT using Next.js (for Next.js, use
neon-auth-nextjs as a starting point and add Data API configuration, or see examples/nextjs-neon-auth/)
Critical Rules
-
Adapter Factory Pattern: Always call adapters with ()
adapter: SupabaseAuthAdapter()
adapter: SupabaseAuthAdapter
-
React Adapter Import: NOT exported from main - use subpath
import { BetterAuthReactAdapter } from '@neondatabase/neon-js/auth/react/adapters';
-
Type Safety: Always use Database generic for type-safe queries
const client = createClient<Database>({...});
-
CSS Import: Choose ONE - either /ui/css OR /ui/tailwind, never both
Setup
1. Install
npm install @neondatabase/neon-js
2. Generate Database Types
npx neon-js gen-types --db-url "postgresql://user:pass@host:5432/db" --output src/database.types.ts
CLI Options:
npx neon-js gen-types --db-url <url> [options]
--db-url <url> Database connection string
--output, -o <path> Output file (default: database.types.ts)
--schema, -s <name> Schema to include (repeatable, default: public)
--postgrest-v9-compat Disable one-to-one relationship detection
--query-timeout <duration> Query timeout (e.g., 30s, 1m, default: 15s)
3. Create Client (src/client.ts)
import { createClient } from '@neondatabase/neon-js';
import type { Database } from './database.types';
export const neonClient = createClient<Database>({
auth: {
url: import.meta.env.VITE_NEON_AUTH_URL,
},
dataApi: {
url: import.meta.env.VITE_NEON_DATA_API_URL,
},
});
4. Create Provider (src/providers.tsx)
import { NeonAuthUIProvider } from '@neondatabase/neon-js/auth/react';
import { useNavigate } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { neonClient } from './client';
import '@neondatabase/neon-js/ui/css';
export function Providers({ children }: { children: React.ReactNode }) {
const navigate = useNavigate();
return (
<NeonAuthUIProvider
authClient={neonClient.auth}
navigate={navigate}
redirectTo="/dashboard"
Link={({href, children}) => <Link to={href}>{children}</Link>}
>
{children}
</NeonAuthUIProvider>
);
}
5. Wrap App (src/main.tsx)
import { BrowserRouter } from 'react-router-dom';
import { Providers } from './providers';
import App from './App';
createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<Providers>
<App />
</Providers>
</BrowserRouter>
);
6. Environment Variables (.env.local)
VITE_NEON_AUTH_URL=https://your-auth.neon.tech
VITE_NEON_DATA_API_URL=https://your-data-api.neon.tech/rest/v1
CSS & Styling
Import Options
Without Tailwind (pre-built CSS bundle ~47KB):
import '@neondatabase/neon-js/ui/css';
With Tailwind CSS v4:
@import 'tailwindcss';
@import '@neondatabase/neon-js/ui/tailwind';
IMPORTANT: Never import both - causes duplicate styles.
Dark Mode
<NeonAuthUIProvider
defaultTheme="system"
>
Custom Theming
Override CSS variables in your stylesheet:
:root {
--primary: oklch(0.7 0.15 250);
--primary-foreground: oklch(0.98 0 0);
--background: oklch(1 0 0);
--foreground: oklch(0.1 0 0);
--card: oklch(1 0 0);
--border: oklch(0.9 0 0);
--radius: 0.5rem;
}
.dark {
--background: oklch(0.15 0 0);
--foreground: oklch(0.98 0 0);
}
NeonAuthUIProvider Props
Full configuration:
<NeonAuthUIProvider
authClient={neonClient.auth}
navigate={navigate}
Link={({href, children}) => <Link to={href}>{children}</Link>}
redirectTo="/dashboard"
social={{
providers: ['google'],
}}
emailOTP={true}
emailVerification={true}
magicLink={false}
multiSession={false}
credentials={{ forgotPassword: true }}
signUp={{ fields: ['name'] }}
account={{ fields: ['image', 'name', 'company'] }}
organization={{}}
defaultTheme="system"
localization={{
SIGN_IN: 'Welcome Back',
SIGN_UP: 'Create Account',
}}
>
{children}
</NeonAuthUIProvider>
Database Queries
Select
const { data, error } = await neonClient
.from('todos')
.select('*');
const { data, error } = await neonClient
.from('todos')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: false });
const { data, error } = await neonClient
.from('posts')
.select(`
*,
author:users(name, avatar),
comments(id, content)
`);
const { data, error } = await neonClient
.from('todos')
.select('*')
.eq('id', todoId)
.single();
Insert
const { data, error } = await neonClient
.from('todos')
.insert({ title: 'New todo', user_id: userId })
.select()
.single();
const { data, error } = await neonClient
.from('todos')
.insert([
{ title: 'Todo 1', user_id: userId },
{ title: 'Todo 2', user_id: userId },
])
.select();
Update
const { data, error } = await neonClient
.from('todos')
.update({ completed: true })
.eq('id', todoId)
.select()
.single();
Delete
const { error } = await neonClient
.from('todos')
.delete()
.eq('id', todoId);
Upsert
const { data, error } = await neonClient
.from('profiles')
.upsert({ user_id: userId, bio: 'Updated bio' })
.select()
.single();
Filters
.eq('column', value)
.neq('column', value)
.gt('column', value)
.gte('column', value)
.lt('column', value)
.lte('column', value)
.like('column', '%pattern%')
.ilike('column', '%pattern%')
.in('column', [1, 2, 3])
.contains('tags', ['javascript'])
.containedBy('tags', ['javascript', 'typescript'])
.is('column', null)
.not('column', 'is', null)
.range(0, 9)
Ordering & Pagination
const { data, error } = await neonClient
.from('posts')
.select('*')
.order('created_at', { ascending: false })
.range(0, 9)
.limit(10);
Auth Methods
Default API (BetterAuth)
await neonClient.auth.signUp.email({ email, password, name });
await neonClient.auth.signIn.email({ email, password });
await neonClient.auth.signIn.social({
provider: 'google',
callbackURL: '/dashboard',
});
const session = await neonClient.auth.getSession();
await neonClient.auth.signOut();
With SupabaseAuthAdapter
import { createClient, SupabaseAuthAdapter } from '@neondatabase/neon-js';
const neonClient = createClient<Database>({
auth: {
url: import.meta.env.VITE_NEON_AUTH_URL,
adapter: SupabaseAuthAdapter(),
},
dataApi: {
url: import.meta.env.VITE_NEON_DATA_API_URL,
},
});
await neonClient.auth.signUp({ email, password, options: { data: { name } } });
await neonClient.auth.signInWithPassword({ email, password });
await neonClient.auth.signInWithOAuth({ provider: 'google', options: { redirectTo } });
const { data: session } = await neonClient.auth.getSession();
await neonClient.auth.signOut();
neonClient.auth.onAuthStateChange((event, session) => {
console.log(event);
});
With BetterAuthReactAdapter
import { createClient } from '@neondatabase/neon-js';
import { BetterAuthReactAdapter } from '@neondatabase/neon-js/auth/react/adapters';