Bỏ qua

Edge Functions vs Database Functions

Comparison Overview

Key Differences

┌─────────────────────────────────────────────────────────────┐
│           EDGE FUNCTIONS vs DATABASE FUNCTIONS               │
├──────────────────────────┬──────────────────────────────────┤
│      Edge Functions      │      Database Functions          │
│      (Deno/TS)          │      (PL/pgSQL)                  │
├──────────────────────────┼──────────────────────────────────┤
│  Runs at: Edge network   │  Runs in: PostgreSQL            │
│  Language: TypeScript    │  Language: PL/pgSQL, SQL        │
│  HTTP calls: Easy        │  HTTP calls: Limited            │
│  Latency: + network hop  │  Latency: Direct DB access      │
│  Timeout: 30 seconds     │  Timeout: Configurable          │
│  RLS: Manual check       │  RLS: Native support            │
├──────────────────────────┼──────────────────────────────────┤
│  Best for:               │  Best for:                      │
│  - External APIs         │  - Data operations              │
│  - Webhooks              │  - Triggers                     │
│  - Complex JS logic      │  - RLS helper functions         │
│  - File processing       │  - Computed columns             │
└──────────────────────────┴──────────────────────────────────┘

When to Use Edge Functions

✅ External API Integration

// Edge Function - Calling external API
serve(async (req: Request) => {
  // Easy to call any external API
  const response = await fetch('https://api.stripe.com/v1/customers', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${STRIPE_KEY}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: 'email=user@example.com',
  });

  return new Response(JSON.stringify(await response.json()));
});

✅ Complex Business Logic

// Edge Function - Complex validation/transformation
serve(async (req: Request) => {
  const { order } = await req.json();

  // Complex business logic
  const discount = calculateDiscount(order);
  const tax = calculateTax(order, order.shippingAddress);
  const shipping = await getShippingRate(order);

  // Multiple external calls
  const inventory = await checkInventory(order.items);
  const fraud = await checkFraudRisk(order);

  // Hard to do in PL/pgSQL
  return new Response(JSON.stringify({
    discount,
    tax,
    shipping,
    inventory,
    fraudScore: fraud.score,
  }));
});

✅ Webhook Processing

// Edge Function - Webhook handler
serve(async (req: Request) => {
  // Verify signature
  const signature = req.headers.get('X-Webhook-Signature');
  const body = await req.text();

  if (!verifySignature(body, signature)) {
    return new Response('Invalid signature', { status: 401 });
  }

  // Process webhook payload
  const event = JSON.parse(body);
  await handleWebhookEvent(event);

  return new Response('OK');
});

When to Use Database Functions

✅ Data Operations

-- Database Function - Complex query
CREATE FUNCTION get_user_dashboard(user_uuid UUID)
RETURNS TABLE (
  total_tasks BIGINT,
  completed_tasks BIGINT,
  pending_tasks BIGINT,
  overdue_tasks BIGINT
) AS $$
BEGIN
  RETURN QUERY
  SELECT
    COUNT(*),
    COUNT(*) FILTER (WHERE status = 'done'),
    COUNT(*) FILTER (WHERE status = 'pending'),
    COUNT(*) FILTER (WHERE due_date < NOW() AND status != 'done')
  FROM tasks
  WHERE user_id = user_uuid;
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;

✅ Triggers

-- Database Trigger - Auto-actions
CREATE FUNCTION handle_task_completion()
RETURNS TRIGGER AS $$
BEGIN
  IF NEW.status = 'done' AND OLD.status != 'done' THEN
    NEW.completed_at = NOW();

    -- Update project progress
    UPDATE projects
    SET completed_tasks = completed_tasks + 1
    WHERE id = NEW.project_id;
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

✅ RLS Helper Functions

-- Database Function - RLS helper
CREATE FUNCTION is_project_member(p_project_id UUID)
RETURNS BOOLEAN AS $$
  SELECT EXISTS (
    SELECT 1 FROM project_members
    WHERE project_id = p_project_id
    AND user_id = auth.uid()
  );
$$ LANGUAGE sql STABLE SECURITY DEFINER;

-- Used in RLS policy
CREATE POLICY "Project access" ON tasks
  USING (is_project_member(project_id));

Hybrid Pattern

Combine Both

┌─────────────────────────────────────────────────────────────┐
│                    HYBRID PATTERN                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Client                                                      │
│    │                                                         │
│    ├──▶ Edge Function (external APIs, validation)           │
│    │        │                                                │
│    │        ▼                                                │
│    │    Supabase Client                                     │
│    │        │                                                │
│    │        ├──▶ Database Function (data aggregation)       │
│    │        │                                                │
│    │        └──▶ Direct table access (CRUD)                 │
│    │                                                         │
│    └──▶ Direct to Supabase (simple operations)              │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Example: Order Processing

// Edge Function - Orchestrate order
serve(async (req: Request) => {
  const { items, paymentMethodId } = await req.json();

  // 1. Call Stripe (external API)
  const paymentIntent = await stripe.paymentIntents.create({
    amount: calculateTotal(items),
    payment_method: paymentMethodId,
    confirm: true,
  });

  if (paymentIntent.status !== 'succeeded') {
    return new Response(JSON.stringify({ error: 'Payment failed' }), {
      status: 400,
    });
  }

  // 2. Call Database Function (atomic order creation)
  const { data: order, error } = await supabase.rpc('create_order', {
    items: JSON.stringify(items),
    payment_id: paymentIntent.id,
  });

  // 3. Send confirmation email (external API)
  await sendOrderConfirmation(order);

  return new Response(JSON.stringify({ order }));
});
-- Database Function - Atomic order creation
CREATE FUNCTION create_order(items JSONB, payment_id TEXT)
RETURNS orders AS $$
DECLARE
  new_order orders;
BEGIN
  -- Insert order
  INSERT INTO orders (user_id, payment_id, status)
  VALUES (auth.uid(), payment_id, 'paid')
  RETURNING * INTO new_order;

  -- Insert order items
  INSERT INTO order_items (order_id, product_id, quantity, price)
  SELECT
    new_order.id,
    (item->>'product_id')::UUID,
    (item->>'quantity')::INT,
    (item->>'price')::NUMERIC
  FROM jsonb_array_elements(items) AS item;

  -- Update inventory
  UPDATE products p
  SET stock = stock - (item->>'quantity')::INT
  FROM jsonb_array_elements(items) AS item
  WHERE p.id = (item->>'product_id')::UUID;

  RETURN new_order;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

Decision Matrix

Quick Reference

Scenario Use
Call external API Edge Function
Send email/SMS Edge Function
Handle webhook Edge Function
Complex JS/TS logic Edge Function
Aggregate data Database Function
Trigger on change Database Function
RLS helper Database Function
Atomic transactions Database Function
Computed column Database Function

Performance Comparison

Latency

┌─────────────────────────────────────────────────────────────┐
│                    LATENCY COMPARISON                        │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Direct Query (RPC to DB function):                         │
│  Client ──▶ Supabase API ──▶ PostgreSQL                    │
│  ~50-100ms                                                   │
│                                                              │
│  Edge Function then DB:                                      │
│  Client ──▶ Edge ──▶ Supabase API ──▶ PostgreSQL           │
│  ~100-200ms (extra hop)                                      │
│                                                              │
│  Edge Function for external API:                             │
│  Client ──▶ Edge ──▶ External API                          │
│  Faster than: Client ──▶ External API (CORS issues)        │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Tổng kết

Use Edge Functions When

✅ Calling external APIs (Stripe, SendGrid, etc.)
✅ Processing webhooks
✅ Complex TypeScript/JavaScript logic
✅ File/image processing
✅ Multi-step orchestration

Use Database Functions When

✅ Data aggregation/transformation
✅ Triggers on data changes
✅ RLS policy helpers
✅ Atomic transactions
✅ Performance-critical data operations

Golden Rule

Data operations → Database Function
Everything else → Edge Function

Q&A

  1. Có operations nào đang dùng sai chỗ?
  2. External APIs nào cần integrate?
  3. Cần triggers cho events nào?