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:

Prerequisites

Get your Supabase credentials

In the Supabase dashboard, go to Settings → API. Copy:

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

BlocWeave prompt

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:

BlocWeave prompt

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:

BlocWeave prompt

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:

  1. Create — type a task title and click Create; it should appear in the list immediately
  2. Read — refresh the page; tasks should still be there (they're in the database, not in-memory)
  3. Update — click a status button; the active button should update
  4. 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.