Building SupraDesk — A Fullstack Supabase Project with Next.js & TypeScript
Hi, I’m Saurabh Singh Rajput, a 2nd-year CSE student at IIIT Bhagalpur, passionate about building scalable web apps and exploring full-stack development.
Master Auth, Database, Storage, RLS, Realtime & Edge Functions in one real app.
Why this project?
You’ll learn:
Supabase Auth (email, magic link)
Sessions & middleware in Next.js
Postgres schema design & CRUD
Row Level Security (RLS)
Supabase Storage (private/public buckets, signed URLs)
Realtime channels & presence
Edge functions for server-side logic
Deployment & CI integration
Project overview (what you’ll build)
Levels:
Auth & Protected Routes
Profile system (DB + RLS)
Notes (CRUD & RLS)
Storage (file uploads & signed URLs)
Blogging (publish, comments)
Realtime (live updates & presence)
Edge Functions (server logic)
Admin dashboard & analytics
Quick Tech stack
Next.js (App Router) + TypeScript
Supabase (Auth, Postgres, Storage, Realtime, Edge Functions)
shadcn/ui (optional) for components
Tailwind CSS
Vercel (or Supabase hosting)
Quick code snippets (patterns you’ll reuse)
Init Supabase clients (client & server):
// lib/supabase-browser.ts
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!);
// lib/supabase-server.ts
import { createServerClient } from '@supabase/auth-helpers-nextjs';
export const serverSupabase = (req, res) => createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!, { req, res });
Protect route with middleware (example):
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(req: NextRequest) {
const token = req.cookies['sb:token'];
if (!token && req.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', req.url));
}
return NextResponse.next();
}
Upload file to storage (client):
const { data, error } = await supabase.storage.from('user-uploads').upload(`${userId}/${file.name}`, file);
const publicUrl = supabase.storage.from('user-uploads').getPublicUrl(data.path);
Realtime subscribe to a channel:
const channel = supabase.channel('notes:room')
.on('postgres_changes', { event: '*', schema: 'public', table: 'notes' }, payload => {
// handle change
})
.subscribe();
Final notes
Start Level 1 and don’t skip RLS — it’s the single most critical concept for secure apps. I’ve given a full blueprint below (folders, files, SQL, policies). If you want, I’ll produce starter code for Level 1 (auth + middleware + profile) next.
Happy building — ping when you want Level 1 code. 🔥
📁 Full Project Blueprint — Folder Tree & File Roles
Below is the recommended repo structure followed by descriptions for each important file. This is not full code; it's a complete map you can implement.
supra-desk/
├─ .env # local env (NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_KEY)
├─ package.json
├─ next.config.js
├─ tailwind.config.js
├─ tsconfig.json
├─ supabase/
│ └─ functions/ # Edge functions (Deno)
│ ├─ generate-slug/
│ ├─ notify-comment/
│ └─ cleanup-temp-files/
├─ prisma/ (optional) # if using prisma migration for local dev
│
└─ src/
├─ app/ # Next.js App Router
│ ├─ layout.tsx
│ ├─ page.tsx # landing
│ ├─ login/page.tsx
│ ├─ signup/page.tsx
│ ├─ dashboard/
│ │ ├─ page.tsx # dashboard landing
│ │ ├─ notes/
│ │ │ ├─ page.tsx # notes list
│ │ │ ├─ new/page.tsx # create
│ │ │ └─ [id]/page.tsx# view/edit
│ │ ├─ files/
│ │ │ ├─ page.tsx # file list & uploader
│ │ │ └─ upload/page.tsx
│ │ └─ blogs/
│ │ ├─ page.tsx
│ │ ├─ new/page.tsx
│ │ └─ [id]/page.tsx
│ └─ profile/
│ ├─ page.tsx
│ └─ edit/page.tsx
│
├─ components/
│ ├─ ui/ # design system (shadcn wrappers)
│ ├─ NoteCard.tsx
│ ├─ FileUploader.tsx
│ ├─ AvatarUpload.tsx
│ └─ MarkdownEditor.tsx
│
├─ lib/
│ ├─ supabase-browser.ts # client supabase
│ ├─ supabase-server.ts # server supabase (service key)
│ ├─ db.ts # DB helpers, typed queries
│ └─ realtime.ts
│
├─ styles/
├─ hooks/
│ ├─ useUser.ts
│ └─ useRealtimeNotes.ts
│
├─ utils/
│ └─ slugify.ts
│
└─ middleware.ts # protect dashboard routes
🔧 File-Level Details & Responsibilities
src/lib/supabase-browser.ts
Create Supabase client for browser.
Export
supabaseused in client components (auth, storage uploads, realtime).
src/lib/supabase-server.ts
Create server-side Supabase client (service key). Use for SSR or server actions that need elevated access.
Example for server calls in Next.js route handlers or API routes.
src/app/middleware.ts
Intercepts requests, checks user session cookie, redirect unauthenticated users from protected routes to
/login.Use Supabase cookie session or JWT.
src/app/login/page.tsx & signup/page.tsx
Forms for login and signup.
Use Supabase auth methods:
supabase.auth.signUp({ email, password })supabase.auth.signInWithOtp({ email })for magic linksupabase.auth.signInWithPassword(...)
src/app/dashboard/notes/*
Standard CRUD UIs. Use server/client fetching where appropriate.
Use
supabase.auth.getSession()for server orsupabase.auth.getUser()on client.
src/components/FileUploader.tsx
Handles file selection, progress, and upload to storage.
Use
supabase.storage.from(bucket).upload(path, file).
src/supabase/functions/*
- Edge functions: small Deno scripts to run server-side logic on events or via HTTP.
🧾 SQL Schema (Full) — Core Tables
Run these in Supabase SQL editor (create tables + primary keys + foreign keys).
-- users are managed by Supabase Auth, create profiles table
create table profiles (
id uuid primary key references auth.users (id) on delete cascade,
username text unique,
full_name text,
avatar_url text,
bio text,
created_at timestamptz default now()
);
-- notes table
create table notes (
id uuid primary key default gen_random_uuid(),
user_id uuid references profiles (id) on delete cascade,
title text,
content text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- blogs
create table blogs (
id uuid primary key default gen_random_uuid(),
user_id uuid references profiles (id),
title text,
slug text unique,
content text,
published boolean default false,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
-- comments
create table comments (
id uuid primary key default gen_random_uuid(),
blog_id uuid references blogs(id) on delete cascade,
user_id uuid references profiles(id),
content text,
created_at timestamptz default now()
);
-- files metadata (optional: track uploads)
create table user_files (
id uuid primary key default gen_random_uuid(),
user_id uuid references profiles(id),
bucket text,
path text,
name text,
mime text,
size bigint,
created_at timestamptz default now()
);
🔐 RLS Policies (Essential)
Enable RLS for each table and add policies:
profiles
alter table profiles enable row level security;
create policy "profiles_select_self" on profiles for select
using (auth.uid() = id);
create policy "profiles_update_self" on profiles for update
using (auth.uid() = id);
notes
alter table notes enable row level security;
create policy "notes_select_user" on notes for select
using (auth.uid() = user_id::text); -- adjust if types differ
create policy "notes_insert_user" on notes for insert
with check (auth.uid() = user_id::text);
create policy "notes_update_user" on notes for update
using (auth.uid() = user_id::text);
create policy "notes_delete_user" on notes for delete
using (auth.uid() = user_id::text);
blogs — allow public select for published posts, but restrict update/insert to owner:
alter table blogs enable row level security;
create policy "blogs_select_published_or_owner" on blogs for select
using (published = true OR auth.uid() = user_id::text);
create policy "blogs_manage_own" on blogs for insert, update, delete
using (auth.uid() = user_id::text)
with check (auth.uid() = user_id::text);
storage
- Use Supabase Storage ACLs. For private buckets, only allow owner access via signed URLs. (Supabase handles this; you can use metadata to enforce ownership.)
🔁 Realtime Setup Basics
Use Postgres replication or Supabase Realtime (configured by default).
Listen to
postgres_changesevents for tables:notes,comments.Use channels to broadcast minimal payloads (id + action). Clients can fetch the detail when needed.
🧰 Dev workflow & tips
Local dev
.env.local:NEXT_PUBLIC_SUPABASE_URL=https://xyz.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=anonkey SUPABASE_SERVICE_KEY=service_role_key (dev only)Use
supabase start(if using CLI local dev) or work against a dev project in Supabase UI.
Migrations
Use SQL migrations in
supabase/migrations/or use Prisma for local dev + Supabase for prod.Keep RLS policies as SQL migration files.
Edge Functions
- Write Deno functions under
supabase/functions/*, test locally withsupabase functions serve, deploy withsupabase functions deploy.
Deploy
Vercel for frontend (set env vars).
Supabase manages DB & storage.
CI: on PR, run
pnpm build& run tests.
✅ Step-By-Step Implementation Plan (Level-by-Level tasks)
Level 1: Auth & middleware (1 day)
Create supabase clients
Build signup, login pages
Implement
middleware.tsto protect/dashboardCreate
useUserhook andSessionProviderin layout
Level 2: Profile (1 day)
Create
profilestable + RLSCreate edit profile UI & Avatar upload flow
Save avatar in
user-uploadsbucket & store path inprofiles.avatar_url
Level 3: Notes CRUD (2 days)
Create
notestable + RLSBuild notes listing, create, edit, delete
Use server actions or API routes for secure operations
Add pagination, search
Level 4: Storage (1 day)
Configure buckets:
user-uploads(private) +public-assets(public)Build
FileUploadercomponentStore metadata in
user_filestableImplement signed url downloads
Level 5: Blogging & comments (2 days)
blogs,commentstables + policiesEditor (Markdown) + image embeds
Slug generation (edge func or server-side)
Publish/unpublish flow
Level 6: Realtime (1-2 days)
Subscribe to
notes&commentschangesPresence: track online users in a
presencetable or Realtime presenceAdd optimistic UI updates
Level 7: Edge Functions (1 day)
Implement
generate-slugfunctionImplement
notify-new-commentto call email service (via service role or external provider)
Level 8: Admin (1 day)
Create admin-only views, analytics queries
Restrict to admin role via
is_adminflag and policies
🎯 Useful Developer Utilities (recommended)
zodfor schema validationreact-query/ SWR for caching@supabase/auth-helpers-nextjsfor session handlingshadcn/uifor components or Chakra/Headless UIremark/react-markdownfor blog renderingprettier,eslint&huskyfor linting & commit hooks
📦 Example Environment variables
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
SUPABASE_SERVICE_KEY (server only)
NEXT_PUBLIC_VERCEL_URL (for images/absolute paths)
✅ Testing + QA
Unit test business logic (slug generator, DB helpers)
Integration test for API routes
Manual QA of RLS by creating multiple user accounts and verifying access
Run staging deployment (Vercel preview) and check deployment logs
🔐 Security & Production Notes
Never expose
service_rolekey to client; keep server-only.RLS is the most important security mechanism — enforce it strictly.
For private files use signed URLs; don’t store files in public buckets unless intended.
Rate-limit critical edge functions.