This project showcases how to build a full-stack application using Supabase's frontend client library directly for both authentication and database operations - demonstrating the simplicity and power of Supabase's client-side SDK.
It was built for the course Studio Web and Mobile I at HSLU Digital Ideation in Autumn 2025 by Nick Schneeberger.
This application uses Supabase's frontend client library directly in Vue components for both authentication and todo management. This approach:
- Simplified architecture - Direct database access from components using Supabase SDK
- Rapid development - No need for custom API routes or backend logic
- Built-in authentication - Leverage Supabase's auth system seamlessly
β οΈ Requires RLS policies - Database security is handled by Supabase Row Level Security
The application uses a simple, straightforward architecture:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Vue Components (Pages & Components) β
β β’ app/pages/index.vue (Todo list + User profile) β
β β’ app/pages/login.vue (Authentication) β
β β’ app/pages/register.vue (User registration) β
β β
β Components interact directly with Supabase client β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Supabase Client (Frontend Library) β
β β’ app/composables/useSupabaseClient.js β
β β’ Handles all auth & database operations β
β β’ Uses anon key (public, safe to expose) β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Supabase Cloud β
β β’ Authentication & user management β
β β’ PostgreSQL database with todos table β
β β’ Row Level Security (RLS) policies for security β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Why This Approach Works Well:
- Perfect for apps with user-specific data (todos belong to users)
- Supabase handles authentication and authorization automatically
- RLS policies ensure users can only access their own data
- No custom backend code needed - focus on the frontend
This project uses Supabase's anon/public key with authentication and RLS:
- Secure authentication - Built-in email/password authentication
- Row Level Security - Database policies ensure data isolation per user
- Public key is safe - Designed to be used in the browser
- User context - Supabase automatically tracks the authenticated user
Note: You must configure proper RLS policies in Supabase to ensure users can only access their own todos. This provides security without custom backend logic.
- User Authentication - Register, login, logout functionality
- User Profile - Display logged-in user's email on main page
- Create new todos (tied to authenticated user)
- Read todos from database (user-specific, automatically filtered by RLS)
- Update todo status (mark as complete)
- Delete todos
- Auth Protection - Redirects to login if not authenticated
- Input validation on the frontend
- Error handling with user-friendly messages
- Empty state when no todos exist
- Row Level Security - Users can only access their own todos (database-level security)
stuw1-demo-todo-with-login/
βββ app/
β βββ pages/
β β βββ index.vue # Main page (todos + user profile)
β β βββ login.vue # Login page
β β βββ register.vue # Registration page
β βββ composables/
β β βββ useSupabaseClient.js # Supabase client setup
β βββ components/
β βββ TodoForm.vue # Todo input form component (optional)
β βββ TodoList.vue # Todo list display component (optional)
βββ nuxt.config.ts # Nuxt configuration
βββ package.json # Dependencies
βββ README.md # This file
- Node.js 18+ installed
- A Supabase account and project (free tier works!)
git clone https://github.com/digitalideation/stuw1-demo-todo-with-login.git
cd stuw1-demo-todo-with-loginnpm installIn your Supabase project, create a table named todos:
CREATE TABLE todos (
id BIGSERIAL PRIMARY KEY,
task TEXT NOT NULL,
done BOOLEAN DEFAULT false,
user_id UUID REFERENCES auth.users NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);Enable RLS and create policies to ensure users can only access their own todos:
-- Enable Row Level Security
ALTER TABLE todos ENABLE ROW LEVEL SECURITY;
-- Policy: Users can view their own todos
CREATE POLICY "Users can view their own todos"
ON todos FOR SELECT
USING (auth.uid() = user_id);
-- Policy: Users can insert their own todos
CREATE POLICY "Users can insert their own todos"
ON todos FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Policy: Users can update their own todos
CREATE POLICY "Users can update their own todos"
ON todos FOR UPDATE
USING (auth.uid() = user_id);
-- Policy: Users can delete their own todos
CREATE POLICY "Users can delete their own todos"
ON todos FOR DELETE
USING (auth.uid() = user_id);- Go to your Supabase project dashboard
- Navigate to Settings β API
- Copy your:
- Project URL
anon publickey (β This is the public key, safe for browser use)
Update the app/composables/useSupabaseClient.js file with your credentials:
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
"https://your-project.supabase.co",
"your-anon-public-key-here"
);- The anon/public key is safe to use in the browser
- RLS policies protect your data at the database level
- Never use the service_role key in frontend code
npm run devIf that doesn't work, try:
nuxt devThe application will be available at http://localhost:3000
Let's trace what happens when you add a new todo:
- User types in the input field and clicks "Add"
index.vuevalidates the input and callsaddTodo()- Supabase client directly inserts the todo into the database:
- Uses the authenticated user's session automatically
- Supabase checks RLS policies to ensure user has permission
- Database inserts the todo with the user's
user_id
index.vuefetches the updated todo list from Supabase- Component re-renders with the new todo displayed
Authentication Flow:
- User registers/logs in via
login.vueorregister.vue - Supabase creates an auth session stored in browser
- User is redirected to
index.vuewhich shows their profile and todos - All subsequent database requests include the user's session automatically
- RLS policies ensure users only see/modify their own todos
- User can logout directly from the index page
How security works in this app:
- Row Level Security (RLS) - Database-level security policies ensure data isolation
- Authenticated sessions - Supabase manages JWT tokens automatically
- Public key is safe - The anon/public key is designed for browser use
- User context - RLS policies use
auth.uid()to filter data per user - Automatic filtering - Users automatically only see their own todos (no manual filtering needed in code!)
Why this is secure:
- Users can only access their own todos (enforced by RLS policies at the database level)
- No sensitive keys exposed in the frontend
- Supabase handles authentication, session management, and security
- Even if someone inspects the network requests, they can't access other users' data
- The
user_idcolumn is automatically set and checked by RLS policies
Best practices:
- Always enable RLS on tables containing user data
- Test your RLS policies thoroughly
- Use Supabase's built-in auth features (email verification, password reset, etc.)
- Add rate limiting for production apps (Supabase has this built-in for auth)
One of the most powerful features of this approach is that you don't need to manually filter data by user. Notice in index.vue that we simply query:
const { data } = await supabase.from("todos").select("*");We don't need to do:
// NOT NEEDED with RLS! β
const { data } = await supabase
.from("todos")
.select("*")
.eq("user_id", user.id);Why? Because the RLS policies automatically filter the results based on the authenticated user's session! Supabase knows who is logged in and only returns/allows modifications to that user's data. This makes your code simpler AND more secure.
MIT - Feel free to use this for educational purposes!
This is an educational project. Feel free to fork and experiment!
Happy Coding! π