Overview
EpiNeko uses Supabase for authentication, database management, and Row Level Security (RLS). The integration provides separate client and server utilities for optimal performance and security.
Supabase utilities are located in src/utils/supabase/ with separate implementations for client-side, server-side, and middleware usage.
Environment Variables
Add these environment variables to your .env.local file:
NEXT_PUBLIC_SUPABASE_URL = your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY = your_supabase_anon_key
Both environment variables are required. The application will throw an error if they are missing.
Client-Side Usage
Use the client-side Supabase client for browser-based operations in Client Components.
Creating a Client
// src/utils/supabase/client.ts:3
import { createClient } from '@/utils/supabase/client' ;
const supabase = createClient ();
Implementation Details
// src/utils/supabase/client.ts:1
import { createBrowserClient } from '@supabase/ssr'
export function createClient () {
const supabaseUrl = process . env . NEXT_PUBLIC_SUPABASE_URL ;
const supabaseKey = process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY ;
if ( ! supabaseUrl || ! supabaseKey ) {
throw new Error ( 'Missing Supabase environment variables' );
}
return createBrowserClient (
supabaseUrl ,
supabaseKey
)
}
Usage Examples
Authentication
Database Queries
Real-time Subscriptions
'use client' ;
import { createClient } from '@/utils/supabase/client' ;
export default function LoginForm () {
const supabase = createClient ();
const handleLogin = async ( email : string , password : string ) => {
const { data , error } = await supabase . auth . signInWithPassword ({
email ,
password ,
});
if ( error ) {
console . error ( 'Login error:' , error . message );
return ;
}
console . log ( 'Logged in user:' , data . user );
};
return (
< form onSubmit = {(e) => {
e . preventDefault ();
const formData = new FormData ( e . currentTarget );
handleLogin (
formData . get ( 'email' ) as string ,
formData . get ( 'password' ) as string
);
}} >
{ /* form fields */ }
</ form >
);
}
Server-Side Usage
Use the server-side Supabase client for Server Components, Route Handlers, and Server Actions.
Creating a Server Client
// src/utils/supabase/server.ts:4
import { createClient } from '@/utils/supabase/server' ;
const supabase = await createClient ();
The server client is an async function that must be awaited. It handles cookie management automatically.
Implementation Details
// src/utils/supabase/server.ts:1
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 in Server Components
// Middleware will handle session refresh
}
},
},
}
)
}
Usage Examples
Server Component
Server Action
Route Handler
// app/dashboard/page.tsx
import { createClient } from '@/utils/supabase/server' ;
import { redirect } from 'next/navigation' ;
export default async function DashboardPage () {
const supabase = await createClient ();
// Get authenticated user
const { data : { user } } = await supabase . auth . getUser ();
if ( ! user ) {
redirect ( '/login' );
}
// Fetch user's library
const { data : library } = await supabase
. from ( 'user_library' )
. select ( '*' )
. order ( 'updated_at' , { ascending: false });
return (
< div >
< h1 > Welcome , { user . email } </ h1 >
< LibraryList items = { library } />
</ div >
);
}
Middleware Integration
The middleware updates the user session on every request, ensuring authentication state is always fresh.
Implementation
// src/utils/supabase/middleware.ts:1
import { createServerClient } from '@supabase/ssr'
import { NextResponse , type NextRequest } from 'next/server'
export async function updateSession ( request : NextRequest ) {
let supabaseResponse = NextResponse . next ({
request ,
})
const supabase = createServerClient (
process . env . NEXT_PUBLIC_SUPABASE_URL ! ,
process . env . NEXT_PUBLIC_SUPABASE_ANON_KEY ! ,
{
cookies: {
getAll () {
return request . cookies . getAll ()
},
setAll ( cookiesToSet ) {
cookiesToSet . forEach (({ name , value }) =>
request . cookies . set ( name , value )
)
supabaseResponse = NextResponse . next ({ request })
cookiesToSet . forEach (({ name , value , options }) =>
supabaseResponse . cookies . set ( name , value , options )
)
},
},
}
)
// Refresh the auth token
await supabase . auth . getUser ()
return supabaseResponse
}
Using in middleware.ts
// middleware.ts
import { updateSession } from '@/utils/supabase/middleware'
export async function middleware ( request : NextRequest ) {
return await updateSession ( request )
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.* \\ .(?:svg|png|jpg|jpeg|gif|webp)$).*)' ,
],
}
The middleware automatically refreshes the user session on every request, keeping authentication state synchronized between client and server.
Authentication Patterns
Sign Up
Email/Password
OAuth (Google)
const { data , error } = await supabase . auth . signUp ({
email: 'user@example.com' ,
password: 'securepassword' ,
options: {
data: {
full_name: 'John Doe' ,
username: 'johndoe' ,
}
}
})
Sign In
const { data , error } = await supabase . auth . signInWithPassword ({
email: 'user@example.com' ,
password: 'securepassword' ,
})
Sign Out
const { error } = await supabase . auth . signOut ()
Get Current User
const { data : { user } } = await supabase . auth . getUser ()
Database Operations
Insert
const { data , error } = await supabase
. from ( 'user_library' )
. insert ({
anime_id_jikan: 5114 ,
title: 'Fullmetal Alchemist: Brotherhood' ,
status: 'watching' ,
score: 9
})
. select ()
. single ()
Select
// Select all
const { data , error } = await supabase
. from ( 'user_library' )
. select ( '*' )
// Select with filters
const { data , error } = await supabase
. from ( 'user_library' )
. select ( '*' )
. eq ( 'status' , 'watching' )
. order ( 'updated_at' , { ascending: false })
Update
const { data , error } = await supabase
. from ( 'user_library' )
. update ({ score: 10 , status: 'completed' })
. eq ( 'anime_id_jikan' , 5114 )
. select ()
. single ()
Delete
const { error } = await supabase
. from ( 'user_library' )
. delete ()
. eq ( 'anime_id_jikan' , 5114 )
Type Safety
Generate TypeScript types from your database schema:
npx supabase gen types typescript --project-id your-project-ref > src/types/database.types.ts
Then use them in your code:
import type { Database } from '@/types/database.types'
const supabase = createClient < Database >()
// Now all queries are fully typed
const { data } = await supabase
. from ( 'user_library' )
. select ( '*' )
// data is typed as Database['public']['Tables']['user_library']['Row'][]
Best Practices
Show 1. Use Appropriate Client
Client Component : Use @/utils/supabase/client
Server Component : Use @/utils/supabase/server
Middleware : Use @/utils/supabase/middleware
Show 2. Handle Authentication State
// Always check for user before protected operations
const { data : { user } } = await supabase . auth . getUser ()
if ( ! user ) {
// Handle unauthenticated state
redirect ( '/login' )
}
const { data , error } = await supabase
. from ( 'user_library' )
. select ( '*' )
if ( error ) {
console . error ( 'Database error:' , error . message )
// Handle error appropriately
}
Always enable Row Level Security on tables. See Row Level Security for details.
Common Issues
Error: Missing Supabase environment variables Ensure both NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY are set in your .env.local file.
Error: Cookie manipulation in Server Components This warning is expected and can be ignored. The middleware handles session refresh automatically.
Database Schema View complete database schema
Row Level Security Learn about RLS policies
Supabase Docs Official Supabase documentation
Library Service High-level library operations