Overview
Deltalytix uses Supabase as its backend-as-a-service platform, providing authentication, PostgreSQL database access, file storage, and real-time capabilities.
Configuration
Environment Variables
Add these variables to your .env file:
# Supabase Configuration
NEXT_PUBLIC_SUPABASE_URL = 'https://your-project.supabase.co'
NEXT_PUBLIC_SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
# Database Configuration
DATABASE_URL = "postgresql://username:password@host:port/database?pgbouncer=true&connection_limit=1"
DIRECT_URL = "postgresql://username:password@host:port/database"
The NEXT_PUBLIC_ prefix makes these variables accessible in client-side code. The anon key is safe to expose as it respects Row Level Security (RLS) policies.
Client Initialization
Browser Client
For client-side components (lib/supabase.ts):
import { createBrowserClient } from '@supabase/ssr'
export function createClient () {
return createBrowserClient (
process . env . NEXT_PUBLIC_SUPABASE_URL ! ,
process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY !
)
}
Server Client
For server-side operations (server/auth.ts):
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient () {
const cookieStore = await cookies ()
return createServerClient (
process . env . NEXT_PUBLIC_SUPABASE_URL ! ,
process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY ! ,
{
cookies: {
getAll () {
return cookieStore . getAll ()
},
setAll ( cookiesToSet ) {
try {
cookiesToSet . forEach (({ name , value , options }) =>
cookieStore . set ( name , value , options )
)
} catch {
// Ignore errors from Server Components
}
},
},
}
)
}
Always use createServerClient for server-side code and createBrowserClient for client-side code. Mixing them will cause authentication issues.
Authentication
Supported Providers
Deltalytix supports multiple authentication methods:
Email/Password
Magic Link (OTP)
OAuth (Google, Discord)
Email/Password Authentication
Sign Up with Password
export async function signUpWithPasswordAction (
email : string ,
password : string ,
next : string | null = null ,
locale ?: string
) {
const supabase = await createClient ()
const websiteURL = await getWebsiteURL ()
const { data , error } = await supabase . auth . signUp ({
email ,
password ,
options: {
emailRedirectTo: ` ${ websiteURL } api/auth/callback` ,
},
})
if ( error ) throw new Error ( error . message )
// If email confirmation is disabled, user is auto-signed in
if ( data . user && data . session ) {
await ensureUserInDatabase ( data . user , locale )
}
return { success: true , next }
}
Sign In with Password
export async function signInWithPasswordAction (
email : string ,
password : string ,
next : string | null = null ,
locale ?: string
) {
const supabase = await createClient ()
const { data , error } = await supabase . auth . signInWithPassword ({
email ,
password
})
if ( error ) {
// Auto-create account if user doesn't exist
if ( error . message . includes ( 'Invalid login credentials' )) {
const { data : signUpData , error : signUpError } =
await supabase . auth . signUp ({ email , password })
if ( signUpError ) throw new Error ( signUpError . message )
} else {
throw new Error ( error . message )
}
}
const { data : { user } } = await supabase . auth . getUser ()
if ( user ) {
await ensureUserInDatabase ( user , locale )
}
return { success: true , next }
}
The sign-in function automatically creates an account if the user doesn’t exist, providing a seamless experience.
Magic Link (OTP) Authentication
export async function signInWithEmail (
email : string ,
next : string | null = null ,
locale ?: string
) {
const supabase = await createClient ()
const websiteURL = await getWebsiteURL ()
const { error } = await supabase . auth . signInWithOtp ({
email: email ,
options: {
emailRedirectTo: ` ${ websiteURL } api/auth/callback` ,
},
})
if ( error ) throw new Error ( error . message )
}
Verify OTP
export async function verifyOtp (
email : string ,
token : string ,
type : 'email' | 'signup' = 'email'
) {
const supabase = await createClient ()
const { data , error } = await supabase . auth . verifyOtp ({
email ,
token ,
type
})
if ( data . user && data . session ) {
await ensureUserInDatabase ( data . user , locale )
}
if ( error ) throw new Error ( error . message )
return data
}
OAuth Authentication
Google Sign-In
export async function signInWithGoogle (
next : string | null = null ,
locale ?: string
) {
const supabase = await createClient ()
const websiteURL = await getWebsiteURL ()
const { data , error } = await supabase . auth . signInWithOAuth ({
provider: 'google' ,
options: {
queryParams: {
prompt: 'select_account' ,
},
redirectTo: ` ${ websiteURL } api/auth/callback` ,
},
})
if ( data . url ) {
redirect ( data . url )
}
}
Discord Sign-In
export async function signInWithDiscord (
next : string | null = null ,
locale ?: string
) {
const supabase = await createClient ()
const websiteURL = await getWebsiteURL ()
const { data , error } = await supabase . auth . signInWithOAuth ({
provider: 'discord' ,
options: {
redirectTo: ` ${ websiteURL } api/auth/callback` ,
},
})
if ( data . url ) {
redirect ( data . url )
}
}
Account Linking
Users can link multiple authentication providers to a single account:
export async function linkGoogleAccount () {
const supabase = await createClient ()
const websiteURL = await getWebsiteURL ()
const { data , error } = await supabase . auth . linkIdentity ({
provider: 'google' ,
options: {
redirectTo: ` ${ websiteURL } api/auth/callback?action=link` ,
},
})
if ( data . url ) redirect ( data . url )
if ( error ) throw new Error ( error . message )
}
export async function unlinkIdentity ( identity : any ) {
const supabase = await createClient ()
const { error } = await supabase . auth . unlinkIdentity ( identity )
if ( error ) throw new Error ( error . message )
return { success: true }
}
User Management
Get Current User
export async function getUserId () : Promise < string > {
const supabase = await createClient ()
const { data : { user }, error } = await supabase . auth . getUser ()
if ( error || ! user ) {
throw new Error ( "User not authenticated" )
}
return user . id
}
Sign Out
export async function signOut () {
const supabase = await createClient ()
await supabase . auth . signOut ()
redirect ( '/' )
}
Update User Password
export async function setPasswordAction ( newPassword : string ) {
const supabase = await createClient ()
const { data : { user } } = await supabase . auth . getUser ()
if ( ! user ) throw new Error ( 'User not authenticated' )
const { data , error } = await supabase . auth . updateUser ({
password: newPassword
})
if ( error ) throw new Error ( error . message )
return { success: true }
}
Database Integration
User Synchronization
The ensureUserInDatabase() function syncs Supabase Auth users with your application database:
export async function ensureUserInDatabase ( user : User , locale ?: string ) {
if ( ! user ?. id ) {
await signOut ()
throw new Error ( 'User ID is required' )
}
// Find user by auth_user_id
const existingUserByAuthId = await prisma . user . findUnique ({
where: { auth_user_id: user . id },
})
if ( existingUserByAuthId ) {
// Update if needed
const shouldUpdateEmail = existingUserByAuthId . email !== user . email
const shouldUpdateLanguage = !! locale && locale !== existingUserByAuthId . language
if ( shouldUpdateEmail || shouldUpdateLanguage ) {
return await prisma . user . update ({
where: { auth_user_id: user . id },
data: {
email: shouldUpdateEmail ? user . email : existingUserByAuthId . email ,
language: shouldUpdateLanguage ? locale : existingUserByAuthId . language
},
})
}
return existingUserByAuthId
}
// Create new user
const newUser = await prisma . user . create ({
data: {
auth_user_id: user . id ,
email: user . email || '' ,
id: user . id ,
language: locale || 'en'
},
})
// Create default dashboard layout
await createDefaultDashboardLayout ( user . id )
return newUser
}
This function is called automatically after successful authentication to ensure consistency between Supabase Auth and your application database.
Database Connection
Deltalytix uses Prisma with Supabase’s PostgreSQL database:
import { PrismaClient } from '@prisma/client'
import { PrismaPg } from '@prisma/adapter-pg'
import pg from 'pg'
const pool = new pg . Pool ({
connectionString: process . env . DATABASE_URL ,
})
const adapter = new PrismaPg ( pool )
const prisma = new PrismaClient ({ adapter })
Security Best Practices
Row Level Security (RLS)
Always enable Row Level Security on tables containing user data: ALTER TABLE users ENABLE ROW LEVEL SECURITY ;
CREATE POLICY "Users can view own data"
ON users FOR SELECT
USING ( auth . uid () = auth_user_id);
CREATE POLICY "Users can update own data"
ON users FOR UPDATE
USING ( auth . uid () = auth_user_id);
Use Service Role Key Carefully
The service role key bypasses RLS. Only use it in trusted server-side code: // DON'T use service role key in client-side code
// DO use it for admin operations on the server
const supabaseAdmin = createClient (
process . env . NEXT_PUBLIC_SUPABASE_URL ! ,
process . env . SUPABASE_SERVICE_ROLE_KEY ! // Server-side only
)
Authentication Security
Session Management : Sessions are automatically managed via HTTP-only cookies
Token Refresh : Access tokens are automatically refreshed before expiration
PKCE Flow : OAuth uses PKCE for enhanced security
Rate Limiting : Enable rate limiting in Supabase Dashboard
Error Handling
Handle authentication errors gracefully:
function handleAuthError ( error : any ) : never {
// Check for JSON parsing errors (indicates service issues)
if (
error ?. message ?. includes ( 'Unexpected token' ) ||
error ?. message ?. includes ( 'is not valid JSON' )
) {
throw new Error (
'Authentication service is temporarily unavailable. Please try again.'
)
}
// Re-throw other errors
throw error
}
Testing
Local Development
Create a separate Supabase project for development
Use test credentials in .env.local:
NEXT_PUBLIC_SUPABASE_URL = 'https://test-project.supabase.co'
NEXT_PUBLIC_SUPABASE_ANON_KEY = 'eyJhbGciOiJIUz...'
Test Users
Create test users in your development Supabase project:
// Test user creation
const { data , error } = await supabase . auth . signUp ({
email: '[email protected] ' ,
password: 'test-password-123' ,
options: {
data: {
full_name: 'Test User' ,
}
}
})
Database Migrations
Run migrations using Prisma:
# Generate migration
pnpm prisma migrate dev --name add_new_field
# Apply migrations
pnpm prisma migrate deploy
# Reset database (development only)
pnpm prisma migrate reset
Common Issues
Authentication redirect loops
Check that callback URL is correctly configured in Supabase Dashboard
Verify NEXT_PUBLIC_SITE_URL environment variable
Ensure cookies are enabled in the browser
Database connection timeouts
Use connection pooling with pgbouncer=true in connection string
Set appropriate connection_limit (recommended: 1 for serverless)
Check Supabase project limits in Dashboard
RLS policies blocking queries
Verify user is authenticated: supabase.auth.getUser()
Check RLS policies match your query patterns
Use service role key for admin operations (server-side only)
Ensure ensureUserInDatabase() is called after authentication
Check for unique constraint violations
Verify auth_user_id column exists and is indexed
Additional Resources
Supabase Documentation Official Supabase documentation
Supabase Auth Guide Complete authentication guide
Row Level Security RLS policies and best practices
Supabase Dashboard Manage your Supabase projects