VibeFast
Core Concepts

Backend

Learn the basics of your shared backend—Convex or Supabase—and how to work with the database.

Your VibeFast backend lives in packages/backend/. The structure depends on which backend you chose during setup.

Convex Backend

All your backend code lives in the packages/backend/convex/ directory. Convex provides a real-time database and serverless functions in one seamless package.

Queries, Mutations, and Actions

Convex functions are organized into three types:

TypePurposeCan Modify Data?
QueriesRead data from the database. They are fast and automatically reactive.No
MutationsWrite, update, or delete data. They are transactional and atomic.Yes
ActionsRun server-side code with side effects, like calling third-party APIs.Yes

How-To: Add a New Field to a Table

Let's add a new isFavorite field to the recordings table.

Step 1: Modify the Schema

The single source of truth for your database is convex/schema.ts:

convex/schema.ts
recordings: defineTable({
  userId: v.id('users'),
  name: v.optional(v.string()),
  fileUri: v.string(),
  duration: v.number(),
  createdAt: v.number(),
  status: v.union(v.literal('draft'), v.literal('completed')),
  metering: v.optional(v.array(v.number())),
  isFavorite: v.optional(v.boolean()), // <-- ADD THIS LINE
}).index('by_userId', ['userId']),

Save the file and npx convex dev will automatically push the schema change.

Step 2: Create a Mutation

Add a new mutation in convex/recordingFunctions.ts:

convex/recordingFunctions.ts
export const toggleFavoriteStatus = mutation({
  args: {
    recordingId: v.id('recordings'),
    isFavorite: v.boolean(),
  },
  handler: async (ctx, { recordingId, isFavorite }) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error('Not authenticated');

    const recording = await ctx.db.get(recordingId);
    if (recording?.userId !== userId) throw new Error('Not authorized');

    await ctx.db.patch(recordingId, { isFavorite });
  },
});

Step 3: Use in the Frontend

src/features/voice-notes/components/recording-list-item.tsx
import { useMutation } from 'convex/react';
import { api } from 'convex/_generated/api';

const toggleFavorite = useMutation(api.recordingFunctions.toggleFavoriteStatus);

const handleFavoritePress = () => {
  toggleFavorite({
    recordingId: recording._id,
    isFavorite: !recording.isFavorite,
  });
};

Supabase Backend

Your backend code lives in packages/backend/. It includes:

  • supabase/migrations/ — SQL schema migrations
  • supabase/functions/ — Edge Functions (Deno)
  • src/services/ — TypeScript service layer

Database + RLS

Supabase uses PostgreSQL with Row-Level Security. Queries run directly from the client but are filtered by RLS policies.

ConceptDescription
TablesDefined in SQL migrations
RLS PoliciesSQL rules that filter data per-user
Edge FunctionsDeno functions for AI operations
ServicesTypeScript wrappers for common operations

How-To: Add a New Field to a Table

Let's add a is_favorite field to the recordings table.

Step 1: Create a Migration

Create a new migration file:

cd packages/backend
supabase migration new add_is_favorite_to_recordings

Add the SQL:

supabase/migrations/xxx_add_is_favorite_to_recordings.sql
ALTER TABLE recordings
ADD COLUMN is_favorite BOOLEAN DEFAULT FALSE;

Apply it:

pnpm db:push

Step 2: Regenerate Types

pnpm db:generate-types

This updates src/types/database.ts with the new column.

Step 3: Update the Service (Optional)

Add a helper function in src/services/recordings.ts:

src/services/recordings.ts
export async function toggleFavorite(
  supabase: SupabaseClient,
  recordingId: string,
  isFavorite: boolean
) {
  const { error } = await supabase
    .from('recordings')
    .update({ is_favorite: isFavorite })
    .eq('id', recordingId);

  if (error) throw error;
}

Step 4: Use in the Frontend

src/features/voice-notes/components/recording-list-item.tsx
import { supabase } from '@/lib/supabase';

const handleFavoritePress = async () => {
  await supabase
    .from('recordings')
    .update({ is_favorite: !recording.is_favorite })
    .eq('id', recording.id);
};

Found an issue or bug in the docs?

Help me improve! If you spot any errors, typos, or have suggestions, please let me know.

Reach out on X/Twitter @zafarbuildzz

On this page