惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
Blog — PlanetScale
Blog — PlanetScale
博客园 - 【当耐特】
博客园_首页
The GitHub Blog
The GitHub Blog
月光博客
月光博客
Hugging Face - Blog
Hugging Face - Blog
有赞技术团队
有赞技术团队
博客园 - 三生石上(FineUI控件)
D
Docker
Stack Overflow Blog
Stack Overflow Blog
WordPress大学
WordPress大学
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Apple Machine Learning Research
Apple Machine Learning Research
Vercel News
Vercel News
酷 壳 – CoolShell
酷 壳 – CoolShell
雷峰网
雷峰网
小众软件
小众软件
I
InfoQ
A
About on SuperTechFans
T
The Blog of Author Tim Ferriss
S
SegmentFault 最新的问题
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - Franky

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Supabase Edge Functions with Deno: Production-Ready Desig...
kanta13jp1 · 2026-04-28 · via DEV Community

kanta13jp1

Supabase Edge Functions with Deno: Production-Ready Design Patterns

Supabase Edge Functions run on Deno. Similar to Node.js, but with subtle differences. Here are the patterns I use running 45+ Edge Functions in production.

Basic Structure

// supabase/functions/my-function/index.ts
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};

serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders });
  }

  try {
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL')!,
      Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
    );

    const body = await req.json();
    // ... logic

    return new Response(
      JSON.stringify({ success: true }),
      { headers: { ...corsHeaders, 'Content-Type': 'application/json' } },
    );
  } catch (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } },
    );
  }
});

Enter fullscreen mode Exit fullscreen mode

The Hub Pattern: Bundle Related Functions

Managing 45 separate functions is expensive. Group related functions into a single hub:

// supabase/functions/schedule-hub/index.ts
serve(async (req) => {
  const { action, ...params } = await req.json();

  switch (action) {
    case 'digest.run':
      return await handleDigest(supabase, params);
    case 'digest.weekly':
      return await handleWeeklyDigest(supabase, params);
    case 'competitor.check':
      return await handleCompetitorCheck(supabase, params);
    default:
      return new Response(
        JSON.stringify({ error: `Unknown action: ${action}` }),
        { status: 400, headers: corsHeaders },
      );
  }
});

Enter fullscreen mode Exit fullscreen mode

Flutter call:

final response = await supabase.functions.invoke(
  'schedule-hub',
  body: {'action': 'digest.run', 'date': DateTime.now().toIso8601String()},
);

Enter fullscreen mode Exit fullscreen mode

Secrets Management

# Local dev: supabase/functions/.env
OPENAI_API_KEY=sk-...
RESEND_API_KEY=re_...

# Production: Supabase Dashboard → Project Settings → Secrets
# or via CLI:
supabase secrets set OPENAI_API_KEY=sk-...

Enter fullscreen mode Exit fullscreen mode

const openaiKey = Deno.env.get('OPENAI_API_KEY');
if (!openaiKey) throw new Error('OPENAI_API_KEY not set');

Enter fullscreen mode Exit fullscreen mode

External API Calls: Retry Pattern

async function fetchWithRetry(
  url: string,
  options: RequestInit,
  maxRetries = 3,
): Promise<Response> {
  let lastError: Error;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      if (response.ok) return response;

      // 429 Rate Limit → exponential backoff
      if (response.status === 429) {
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }

      throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    } catch (error) {
      lastError = error;
      if (attempt < maxRetries) {
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
      }
    }
  }

  throw lastError!;
}

Enter fullscreen mode Exit fullscreen mode

Calling Postgres Functions via RPC

// From Edge Function, call a Postgres function
const { data, error } = await supabase.rpc('get_user_achievements', {
  p_user_id: userId,
  p_limit: 10,
});

Enter fullscreen mode Exit fullscreen mode

-- Postgres side
CREATE OR REPLACE FUNCTION get_user_achievements(
  p_user_id UUID,
  p_limit INT DEFAULT 10
)
RETURNS TABLE (id UUID, title TEXT, completed_at TIMESTAMPTZ)
LANGUAGE sql STABLE
AS $$
  SELECT id, title, completed_at
  FROM development_achievements
  WHERE user_id = p_user_id
  ORDER BY completed_at DESC
  LIMIT p_limit;
$$;

Enter fullscreen mode Exit fullscreen mode

RPC is more type-safe than REST and prevents N+1 queries.

Local Testing

supabase start

supabase functions serve my-function --no-verify-jwt

curl -X POST http://localhost:54321/functions/v1/my-function \
  -H "Content-Type: application/json" \
  -d '{"action": "test"}'

Enter fullscreen mode Exit fullscreen mode

Deployment

supabase functions deploy my-function

# or deploy all (automated via GHA)
supabase functions deploy

Enter fullscreen mode Exit fullscreen mode

# .github/workflows/deploy-prod.yml
- name: Deploy Edge Functions
  run: supabase functions deploy
  env:
    SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
    SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }}

Enter fullscreen mode Exit fullscreen mode

Summary

Design principles:
  1. Hub pattern: bundle related EFs to reduce deploy overhead
  2. Secrets: Deno.env.get + Supabase Secrets (never hardcode)
  3. CORS: always handle OPTIONS preflight
  4. Retries: exponential backoff for 429/500
  5. RPC first: push complex queries into Postgres functions

Enter fullscreen mode Exit fullscreen mode

Keep each Edge Function small. The hub pattern lets you scale past 50 functions without losing your mind.