What you'll build
In this part you'll connect a Supabase database to the task manager and build the core data layer: a tasks table with CRUD operations and a dashboard UI that lists, creates, updates, and deletes tasks.
By the end of this part your app will have:
- A
taskstable in Supabase with the right schema - Server actions for creating, updating, and deleting tasks
- A dashboard page that lists the signed-in user's tasks
- A form for creating new tasks inline
Prerequisites
- Parts 1 and 2 completed — you have a running Next.js app with Firebase Auth
- A Supabase project. Go to supabase.com, click New project, give it a name, choose a region close to your users, and set a database password. Wait for the project to provision (~1 minute).
Get your Supabase credentials
In the Supabase dashboard, go to Settings → API. Copy:
- Project URL — looks like
https://xxxxxxxxxxxx.supabase.co - anon / public key — a long JWT starting with
eyJ... - service_role key — a longer JWT, used server-side only (never expose this to the browser)
Add them to .env.local:
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxxxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...
Create the tasks table
In the Supabase dashboard, go to SQL Editor and run:
create table tasks (
id uuid primary key default gen_random_uuid(),
user_id text not null,
title text not null,
description text,
status text not null default 'todo'
check (status in ('todo', 'in_progress', 'done')),
priority text not null default 'medium'
check (priority in ('low', 'medium', 'high')),
due_date date,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
-- Index for fetching a user's tasks efficiently
create index tasks_user_id_idx on tasks (user_id, created_at desc);
The user_id column stores the Firebase UID from the session cookie — this is how we associate tasks with the signed-in user.
Install the Supabase client
npm install @supabase/supabase-js
Let BlocWeave write the data layer
Add Supabase to this Next.js 14 app. The credentials are in .env.local as NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, and SUPABASE_SERVICE_ROLE_KEY. Create: 1. src/lib/supabase.ts — two client factories: `createBrowserClient()` using the anon key for client components, and `createServerClient()` using the service role key for server components and server actions 2. src/types/index.ts — TypeScript types: Task (matching the tasks table schema), TaskStatus ('todo' | 'in_progress' | 'done'), TaskPriority ('low' | 'medium' | 'high'), CreateTaskInput, UpdateTaskInput 3. src/lib/task-actions.ts — server actions: getTasks(userId), createTask(userId, input), updateTask(id, userId, input), deleteTask(id, userId). All queries filter by userId to ensure users only see their own tasks. 4. src/app/dashboard/page.tsx — a server component that reads the session cookie to get the userId, fetches the user's tasks, and renders a task list. Each task shows its title, status badge, priority, and a delete button. Include a "New task" form at the top with a title field and a create button.
Review what was generated
src/lib/supabase.ts — the server client should use the service role key, not the anon key. Service role bypasses Row Level Security (RLS), which is fine here because our server actions filter by userId manually. We'll add RLS properly in Part 5.
src/lib/task-actions.ts — every query should include a .eq("user_id", userId) filter. If BlocWeave missed this on any query, add a follow-up:
In src/lib/task-actions.ts, make sure every query — getTasks, updateTask, and deleteTask — filters by userId using .eq("user_id", userId). This prevents one user from reading or modifying another user's tasks.
src/app/dashboard/page.tsx — should be a server component (no "use client" at the top). The createTask and deleteTask actions should be wired to <form action={...}> or a client wrapper component.
Add status controls
A task manager isn't useful without a way to update task status. Add inline status controls:
Add a status toggle to each task in src/app/dashboard/page.tsx. Each task should show three buttons — Todo, In Progress, Done — with the active status highlighted. Clicking a button calls the updateTask server action with the new status. Keep the UI minimal — small text buttons in a row, use Tailwind.
Verify the full CRUD flow
With the dev server running, test every operation:
- Create — type a task title and click Create; it should appear in the list immediately
- Read — refresh the page; tasks should still be there (they're in the database, not in-memory)
- Update — click a status button; the active button should update
- Delete — click the delete button; the task should disappear from the list
**Row Level Security is currently disabled** on the tasks table. Any service role query can read all tasks — the only protection right now is the `userId` filter in your server actions. Part 5 adds proper RLS so the database itself enforces the isolation.
Project structure after Part 3
src/
app/
dashboard/
page.tsx ← task list + create form
lib/
supabase.ts ← browser and server client factories
task-actions.ts ← getTasks, createTask, updateTask, deleteTask
types/
index.ts ← Task, TaskStatus, TaskPriority, input types
What's next
In Part 4 we add real-time updates — when a task changes in the database, the dashboard updates instantly without a page refresh. This uses Supabase's Postgres change streams.
BlocWeave