feature: edge functions references (#37)

* rebase and house keeping

* fix edge functions reference files after docs review

* update agents.mdd
This commit is contained in:
Pedro Rodrigues
2026-02-13 15:22:01 +00:00
committed by Pedro Rodrigues
parent af492edaf4
commit 66cfeddf63
18 changed files with 871 additions and 3 deletions

View File

@@ -27,7 +27,8 @@ supabase/
| Priority | Category | Impact | Prefix |
|----------|----------|--------|--------|
| 1 | Database | CRITICAL | `db-` |
| 2 | Realtime | MEDIUM-HIGH | `realtime-` |
| 2 | Edge Functions | HIGH | `edge-` |
| 3 | Realtime | MEDIUM-HIGH | `realtime-` |
| 3 | Storage | HIGH | `storage-` |
Reference files are named `{prefix}-{topic}.md` (e.g., `query-missing-indexes.md`).
@@ -54,6 +55,23 @@ Reference files are named `{prefix}-{topic}.md` (e.g., `query-missing-indexes.md
- `references/db-security-functions.md`
- `references/db-security-service-role.md`
**Edge Functions** (`edge-`):
- `references/edge-adv-regional.md`
- `references/edge-adv-streaming.md`
- `references/edge-adv-websockets.md`
- `references/edge-auth-jwt-verification.md`
- `references/edge-auth-rls-integration.md`
- `references/edge-db-direct-postgres.md`
- `references/edge-db-supabase-client.md`
- `references/edge-dbg-limits.md`
- `references/edge-dbg-testing.md`
- `references/edge-fun-project-structure.md`
- `references/edge-fun-quickstart.md`
- `references/edge-pat-background-tasks.md`
- `references/edge-pat-cors.md`
- `references/edge-pat-error-handling.md`
- `references/edge-pat-routing.md`
**Realtime** (`realtime-`):
- `references/realtime-broadcast-basics.md`
- `references/realtime-broadcast-database.md`
@@ -76,4 +94,4 @@ Reference files are named `{prefix}-{topic}.md` (e.g., `query-missing-indexes.md
---
*34 reference files across 3 categories*
*49 reference files across 4 categories*

View File

@@ -29,6 +29,26 @@ Reference the appropriate resource file based on the user's needs:
| Performance | `references/db-perf-*.md` | Indexes (BRIN, GIN), query optimization |
| Security | `references/db-security-*.md` | Service role key, security_definer functions |
### Edge Functions
| Area | Resource | When to Use |
| ---------------------- | ------------------------------------- | -------------------------------------- |
| Quick Start | `references/edge-fun-quickstart.md` | Creating and deploying first function |
| Project Structure | `references/edge-fun-project-structure.md` | Directory layout, shared code, fat functions |
| JWT Authentication | `references/edge-auth-jwt-verification.md` | JWT verification, jose library, middleware |
| RLS Integration | `references/edge-auth-rls-integration.md` | Passing auth context, user-scoped queries |
| Database (supabase-js) | `references/edge-db-supabase-client.md` | Queries, inserts, RPC calls |
| Database (Direct) | `references/edge-db-direct-postgres.md` | Postgres pools, Drizzle ORM |
| CORS | `references/edge-pat-cors.md` | Browser requests, preflight handling |
| Routing | `references/edge-pat-routing.md` | Multi-route functions, Hono framework |
| Error Handling | `references/edge-pat-error-handling.md` | Error responses, validation |
| Background Tasks | `references/edge-pat-background-tasks.md` | waitUntil, async processing |
| Streaming | `references/edge-adv-streaming.md` | SSE, streaming responses |
| WebSockets | `references/edge-adv-websockets.md` | Bidirectional communication |
| Regional Invocation | `references/edge-adv-regional.md` | Region selection, latency optimization |
| Testing | `references/edge-dbg-testing.md` | Deno tests, local testing |
| Limits & Debugging | `references/edge-dbg-limits.md` | Troubleshooting, runtime limits |
### Realtime
| Area | Resource | When to Use |

View File

@@ -10,7 +10,12 @@ queries.
**Impact:** CRITICAL
**Description:** Row Level Security policies, connection pooling, schema design patterns, migrations, performance optimization, and security functions for Supabase Postgres.
## 2. Realtime (realtime)
## 2. Edge Functions (edge)
**Impact:** HIGH
**Description:** Fundamentals, authentication, database access, CORS, routing, error handling, streaming, WebSockets, regional invocations, testing, and limits.
## 3. Realtime (realtime)
**Impact:** MEDIUM-HIGH
**Description:** Channel setup, Broadcast messaging, Presence tracking, Postgres Changes listeners, cleanup patterns, error handling, and debugging.

View File

@@ -0,0 +1,55 @@
---
title: Regional Invocations
impact: LOW-MEDIUM
impactDescription: Optimizes latency for region-specific workloads
tags: edge-functions, regions, latency, performance
---
## Regional Invocations
Execute Edge Functions in specific regions to minimize latency to your database. Regional invocations don't auto-failover during outages.
**Incorrect:**
```typescript
// Assumes automatic failover (it doesn't)
const { data } = await supabase.functions.invoke("fn", {
region: FunctionRegion.EuWest1,
}); // If EU down, this fails
// Function close to user but far from database
// User in Tokyo, database in US East
await supabase.functions.invoke("db-heavy-fn", {
region: FunctionRegion.ApNortheast1, // High latency to US database!
});
```
**Correct:**
```typescript
import { createClient, FunctionRegion } from "@supabase/supabase-js";
// Match function region to database region for db-heavy operations
const DB_REGION = FunctionRegion.UsEast1; // Same as your database
const { data } = await supabase.functions.invoke("db-heavy-fn", {
body: { query: "..." },
region: DB_REGION,
});
// Implement explicit fallback
async function invokeWithFallback(name: string, body: object) {
try {
return await supabase.functions.invoke(name, {
body,
region: FunctionRegion.EuWest1,
});
} catch {
return await supabase.functions.invoke(name, { body }); // Default region
}
}
```
Available regions: us-east-1, us-west-1, us-west-2, eu-west-1, eu-west-2, eu-west-3, eu-central-1, ap-northeast-1, ap-northeast-2, ap-south-1, ap-southeast-1, ap-southeast-2, ca-central-1, sa-east-1.
Reference: [Regional Invocations](https://supabase.com/docs/guides/functions/regional-invocation)

View File

@@ -0,0 +1,57 @@
---
title: Streaming Responses
impact: MEDIUM
impactDescription: Enables real-time data delivery and AI response streaming
tags: edge-functions, streaming, sse, server-sent-events
---
## Streaming Responses
Stream data progressively to clients using ReadableStream or Server-Sent Events.
**Incorrect:**
```typescript
// Missing error handling in stream callback
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode(JSON.stringify({ test: true })));
// No try/catch/finally — errors silently lost, stream never closed
},
});
```
**Correct:**
```typescript
// SSE stream with proper error handling
Deno.serve(async (req) => {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
try {
for (let i = 1; i <= 5; i++) {
// SSE format: "data:" prefix + double newline
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ count: i })}\n\n`));
await new Promise((r) => setTimeout(r, 1000));
}
} catch (err) {
console.error("Stream error:", err);
} finally {
controller.close();
}
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
});
```
Both SSE-formatted streaming (with `data:` prefix) and raw streaming (without prefix) are valid. Use SSE format when consumed by `EventSource`; use raw streaming for AI model responses or binary data.

View File

@@ -0,0 +1,71 @@
---
title: WebSocket Handling
impact: MEDIUM
impactDescription: Enables bidirectional real-time communication
tags: edge-functions, websockets, realtime, bidirectional
---
## WebSocket Handling
Create WebSocket servers for bidirectional communication. Browser clients cannot send custom headers, so pass JWT via query parameters. Deploy with `--no-verify-jwt`.
**Incorrect:**
```typescript
// Browser WebSocket cannot send custom headers
const ws = new WebSocket(url, {
headers: { Authorization: `Bearer ${token}` }, // Won't work!
});
// Sending without checking socket state
socket.send(data); // May throw if socket closed
// Deployed with JWT verification enabled
npx supabase functions deploy websocket // Gateway blocks WebSocket upgrade
```
**Correct:**
```typescript
// Server: WebSocket with query param auth
import { createClient } from "npm:@supabase/supabase-js@2";
Deno.serve(async (req) => {
const url = new URL(req.url);
const jwt = url.searchParams.get("jwt");
if (!jwt) return new Response("Missing token", { status: 403 });
// Verify JWT using getClaims()
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_ANON_KEY")!,
{ global: { headers: { Authorization: `Bearer ${jwt}` } } }
);
const { data, error } = await supabase.auth.getClaims();
if (error || !data.claims?.sub) return new Response("Invalid token", { status: 403 });
// Verify this is a WebSocket upgrade request
const upgrade = req.headers.get("upgrade") || "";
if (upgrade.toLowerCase() !== "websocket") {
return new Response("Expected WebSocket upgrade", { status: 400 });
}
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onmessage = (e) => {
if (socket.readyState === WebSocket.OPEN) {
socket.send(`Echo: ${e.data}`);
}
};
return response;
});
// Client: pass JWT via query param (note: query params may be logged in some logging systems)
const ws = new WebSocket(`wss://PROJECT.supabase.co/functions/v1/ws?jwt=${jwt}`);
```
Deploy: `npx supabase functions deploy websocket --no-verify-jwt`
Reference: [WebSockets Guide](https://supabase.com/docs/guides/functions/websockets)

View File

@@ -0,0 +1,64 @@
---
title: JWT Verification and Authentication
impact: CRITICAL
impactDescription: Prevents unauthorized access and security vulnerabilities
tags: edge-functions, jwt, authentication, jose, security
---
## JWT Verification and Authentication
Secure Edge Functions with JWT verification. By default, the gateway validates JWTs. Use `supabase.auth.getClaims()` for most cases. Use jose library when building custom auth middleware. Disable verification with `--no-verify-jwt` for endpoints that handle auth independently (e.g., webhooks, WebSockets).
**Incorrect:**
```typescript
// Old deprecated pattern using symmetric secret
import { verify } from "djwt";
const payload = await verify(token, JWT_SECRET, "HS256");
```
**Correct:**
```typescript
// Simple: getClaims() verifies via JWKS (initial fetch required, then cached ~10min)
const token = req.headers.get("Authorization")?.replace("Bearer ", "");
const { data, error } = await supabase.auth.getClaims(token);
if (error) return new Response("Invalid JWT", { status: 401 });
const userId = data.claims.sub;
```
`getClaims()` is tied to the supabase-js client and returns a fixed set of claims. Use jose directly when you need full control over verification (custom issuer, audience, clock tolerance) or access to the complete raw JWT payload:
```typescript
import * as jose from "jsr:@panva/jose@6";
const JWKS = jose.createRemoteJWKSet(
new URL(Deno.env.get("SUPABASE_URL")! + "/auth/v1/.well-known/jwks.json")
);
Deno.serve(async (req) => {
if (req.method === "OPTIONS") return new Response("ok"); // Skip auth for preflight
const authHeader = req.headers.get("Authorization");
if (!authHeader?.startsWith("Bearer ")) {
return new Response("Unauthorized", { status: 401 });
}
const issuer =
Deno.env.get("SB_JWT_ISSUER") ?? Deno.env.get("SUPABASE_URL") + "/auth/v1";
const { payload } = await jose.jwtVerify(authHeader.slice(7), JWKS, {
issuer,
});
return new Response(JSON.stringify({ userId: payload.sub }));
});
```
Separately, the API gateway verifies JWTs before they reach your function. Supabase is transitioning toward developer-controlled verification (explicit in-function checks) rather than implicit gateway validation — `verify_jwt` remains supported during this transition. To disable gateway-level verification, use either the CLI flag (`npx supabase functions deploy fn-name --no-verify-jwt`) or `config.toml`:
```toml
[functions.fn-name]
verify_jwt = false
```
Reference: [Securing Functions](https://supabase.com/docs/guides/functions/auth)

View File

@@ -0,0 +1,51 @@
---
title: RLS Integration in Edge Functions
impact: HIGH
impactDescription: Ensures proper data isolation and security enforcement
tags: edge-functions, rls, row-level-security, auth-context
---
## RLS Integration in Edge Functions
Pass user authentication context to Supabase client for Row Level Security enforcement. Create the client inside the request handler to get per-request auth context. Use service role key only for admin operations.
**Incorrect:**
```typescript
// Client created outside handler - same auth for all requests
const supabase = createClient(url, key, {
global: { headers: { Authorization: "static-token" } },
});
Deno.serve(async (req) => {
const { data } = await supabase.from("profiles").select("*");
return Response.json(data);
});
```
**Correct:**
```typescript
import { createClient } from "npm:@supabase/supabase-js@2";
Deno.serve(async (req) => {
// Create client with per-request auth context
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_ANON_KEY")!,
{
global: {
headers: { Authorization: req.headers.get("Authorization")! },
},
}
);
// RLS policies now apply to this user
const { data, error } = await supabase.from("profiles").select("*");
return Response.json({ data, error });
});
```
Use `SUPABASE_SERVICE_ROLE_KEY` for admin operations that need to bypass RLS (background jobs, webhooks). For verifying the user's identity, use `supabase.auth.getClaims()` on the per-request client.
Reference: [Connect to Postgres](https://supabase.com/docs/guides/functions/connect-to-postgres)

View File

@@ -0,0 +1,48 @@
---
title: Direct Postgres Connections
impact: MEDIUM
impactDescription: Enables complex queries and ORM usage
tags: edge-functions, postgres, drizzle, connection-pooling
---
## Direct Postgres Connections
Connect directly to Postgres for complex queries or ORM usage. Use port 6543 (transaction pooler) and disable prepared statements. Always release connections back to the pool.
**Incorrect:**
```typescript
// Wrong port, missing prepare:false, connection leak
import postgres from "postgres";
const client = postgres(connectionString); // Uses session mode port
const db = drizzle(client); // Prepared statements fail
Deno.serve(async () => {
const data = await db.select().from(users);
return Response.json(data);
// Connection never released!
});
```
**Correct:**
```typescript
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
Deno.serve(async () => {
// Disable prefetch for transaction pooler mode
const client = postgres(Deno.env.get("SUPABASE_DB_URL")!, {
prepare: false,
});
const db = drizzle(client);
const data = await db.select().from(users);
return Response.json(data);
});
```
Use `SUPABASE_DB_URL` (auto-injected) for the connection string. When using `deno.land/x/postgres` (not postgres-js), use a pool size of 1.
Reference: [Connect to Postgres](https://supabase.com/docs/guides/functions/connect-to-postgres)

View File

@@ -0,0 +1,56 @@
---
title: Database Access with supabase-js
impact: HIGH
impactDescription: Primary method for database operations with RLS support
tags: edge-functions, database, supabase-js, queries
---
## Database Access with supabase-js
Access Supabase Postgres using the official JavaScript client. Always handle errors and use `.select()` after insert/update to get returned data.
**Incorrect:**
```typescript
// Missing error handling and .select() after insert
const { data } = await supabase.from("posts").insert({ title: "Test" });
console.log(data); // null - no data returned!
// Using .single() when multiple rows might match
const { data } = await supabase
.from("users")
.select("*")
.eq("role", "admin")
.single(); // Crashes if multiple admins!
```
**Correct:**
```typescript
import { createClient } from "npm:@supabase/supabase-js@2";
Deno.serve(async (req) => {
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_ANON_KEY")!,
{ global: { headers: { Authorization: req.headers.get("Authorization")! } } }
);
// Insert with .select() to get returned row
const { data, error } = await supabase
.from("posts")
.insert({ title: "Test" })
.select()
.single();
if (error) {
return Response.json({ error: error.message }, { status: 500 });
}
return Response.json(data);
});
```
Use `.single()` only when expecting exactly one row (e.g., by primary key). For multiple rows, omit it.
Reference: [JavaScript Client](https://supabase.com/docs/reference/javascript/select)

View File

@@ -0,0 +1,52 @@
---
title: Limits and Troubleshooting
impact: HIGH
impactDescription: Prevents production failures and debugging bottlenecks
tags: edge-functions, limits, debugging, troubleshooting
---
## Limits and Troubleshooting
Understand Edge Function limits: 256MB memory, 2s CPU time, 150s (free) or 400s (paid) wall clock, and 150s request idle timeout (triggers 504 if no response sent). Check logs for debugging.
**Incorrect:**
```typescript
// Loading entire large dataset into memory
const allData = await supabase.from("huge_table").select("*"); // Memory exceeded
// CPU-intensive without batching
for (let i = 0; i < 1000000; i++) {
heavyComputation(i); // CPU time exceeded
}
// Await without timeout
const data = await fetch(externalApi); // May hang forever -> 504 timeout
```
**Correct:**
```typescript
// Paginate large queries
const { data } = await supabase
.from("huge_table")
.select("*")
.range(0, 100); // Fetch in batches
// Add timeout to external calls
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const response = await fetch(externalApi, { signal: controller.signal });
clearTimeout(timeout);
// Use background tasks for long operations
EdgeRuntime.waitUntil(processInBatches(items, 100));
return Response.json({ status: "processing" });
```
Debug with: `npx supabase functions serve --inspect-mode brk` (or shorthand `--inspect`). Check `SB_REGION` and `SB_EXECUTION_ID` env vars for tracing (hosted environment only).
Common errors: 504 (timeout), 503 (boot error — syntax/import), 500 (uncaught exception), 401 (invalid JWT), 546 (resource limit — memory/CPU exceeded).
Reference: [Limits](https://supabase.com/docs/guides/functions/limits)

View File

@@ -0,0 +1,63 @@
---
title: Testing Edge Functions
impact: MEDIUM
impactDescription: Ensures reliability before production deployment
tags: edge-functions, testing, deno, unit-tests
---
## Testing Edge Functions
Test Edge Functions locally using Deno's built-in test runner. Run `npx supabase start` first, then `npx supabase functions serve` before running tests.
**Incorrect:**
```typescript
// Running tests without serving functions
deno test --allow-all tests/ // Connection refused!
// Using production credentials
const supabase = createClient(
"https://prod.supabase.co", // Never hardcode production!
"prod-key"
);
// Not cleaning up test data
Deno.test("creates user", async () => {
await supabase.from("users").insert({ email: "test@test.com" });
// Test data accumulates!
});
```
**Correct:**
```typescript
// supabase/functions/tests/hello-world-test.ts
import { assertEquals } from "jsr:@std/assert@1";
import { createClient } from "npm:@supabase/supabase-js@2";
import "jsr:@std/dotenv/load";
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!, // From .env
Deno.env.get("SUPABASE_ANON_KEY")!
);
Deno.test("hello-world returns greeting", async () => {
const { data, error } = await supabase.functions.invoke("hello-world", {
body: { name: "Test" },
});
assertEquals(error, null);
assertEquals(data.message, "Hello Test!");
});
// Cleanup test data
Deno.test("creates and cleans up user", async () => {
const { data } = await supabase.from("users").insert({ email: "test@test.com" }).select().single();
// ... assertions ...
await supabase.from("users").delete().eq("id", data.id);
});
```
Run: `npx supabase start && npx supabase functions serve &` then `deno test --allow-all supabase/functions/tests/`
Reference: [Testing Guide](https://supabase.com/docs/guides/functions/unit-test)

View File

@@ -0,0 +1,45 @@
---
title: Project Structure and Organization
impact: HIGH
impactDescription: Proper organization reduces cold starts and improves maintainability
tags: edge-functions, structure, shared, organization
---
## Project Structure and Organization
Organize Edge Functions with shared code in `_shared/` folder (underscore prefix excludes from deployment). Use hyphens for function names. Combine related functionality into "fat functions" to minimize cold starts.
**Incorrect:**
```bash
# Underscores cause URL issues, missing underscore on shared
supabase/functions/
shared/ # Will be deployed as function!
cors.ts
hello_world/ # Underscores problematic in URLs
index.ts
```
**Correct:**
```bash
# Hyphens for functions, underscore prefix for shared
supabase/functions/
import_map.json # Top-level import map
_shared/ # Excluded from deployment
cors.ts
supabaseClient.ts
hello-world/ # Use hyphens
index.ts
```
For VSCode/Cursor, create `.vscode/settings.json`:
```json
{
"deno.enablePaths": ["./supabase/functions"],
"deno.importMap": "./supabase/functions/import_map.json"
}
```
Reference: [Development Tips](https://supabase.com/docs/guides/functions/development-tips)

View File

@@ -0,0 +1,40 @@
---
title: Edge Functions Quick Start
impact: CRITICAL
impactDescription: Foundation for all Edge Function development
tags: edge-functions, quickstart, deployment, cli, deno
---
## Edge Functions Quick Start
Create and deploy serverless TypeScript functions that run globally at the edge on Deno runtime. Functions use `Deno.serve()` as the handler and have automatic access to environment variables: `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`, and `SUPABASE_DB_URL`.
**Incorrect:**
```typescript
// Missing error handling for JSON parsing - crashes on invalid input
Deno.serve(async (req) => {
const { name } = await req.json();
return new Response(`Hello ${name}`);
});
```
**Correct:**
```typescript
// Handle JSON parsing errors gracefully
Deno.serve(async (req) => {
try {
const { name } = await req.json();
return new Response(JSON.stringify({ message: `Hello ${name}!` }), {
headers: { "Content-Type": "application/json" },
});
} catch {
return new Response("Invalid JSON", { status: 400 });
}
});
```
CLI workflow: `npx supabase functions new hello-world`, then `npx supabase start && npx supabase functions serve` for local dev, and `npx supabase functions deploy hello-world` for production (after `npx supabase login` and `npx supabase link --project-ref PROJECT_ID`).
Reference: [Quickstart Guide](https://supabase.com/docs/guides/functions/quickstart)

View File

@@ -0,0 +1,56 @@
---
title: Background Tasks
impact: MEDIUM-HIGH
impactDescription: Enables fast responses for long-running operations
tags: edge-functions, background, waituntil, async
---
## Background Tasks
Execute long-running operations without blocking responses using `EdgeRuntime.waitUntil()`. Return immediately while processing continues in background. Background tasks still count against wall clock, CPU, and memory limits.
**Incorrect:**
```typescript
// Awaiting waitUntil defeats the purpose
Deno.serve(async (req) => {
await EdgeRuntime.waitUntil(sendEmail()); // Blocks response!
return Response.json({ done: true });
});
// No error handling in background task
EdgeRuntime.waitUntil(riskyOperation()); // Errors silently lost
```
**Correct:**
```typescript
Deno.serve(async (req) => {
const { email, message } = await req.json();
// Start background task without await
EdgeRuntime.waitUntil(
sendEmail(email, message).catch((error) => {
console.error("Background task failed:", error);
})
);
// Return immediately
return Response.json({ status: "Email queued" });
});
// Handle shutdown for long-running tasks
addEventListener("beforeunload", (event) => {
console.log("Shutting down:", event.detail?.reason);
});
// Catch unhandled promise rejections in background tasks
addEventListener("unhandledrejection", (ev) => {
console.log("unhandledrejection", ev.reason);
ev.preventDefault();
});
```
For local development, set `[edge_runtime] policy = "per_worker"` in config.toml (disables hot reload; requires manual restart via `npx supabase functions serve`).
Reference: [Background Tasks](https://supabase.com/docs/guides/functions/background-tasks)

View File

@@ -0,0 +1,59 @@
---
title: CORS Configuration
impact: HIGH
impactDescription: Required for browser-based function invocation
tags: edge-functions, cors, browser, preflight
---
## CORS Configuration
Handle Cross-Origin Resource Sharing for browser-invoked Edge Functions. Always handle OPTIONS preflight requests and include CORS headers in all responses including errors.
**Incorrect:**
```typescript
// Missing OPTIONS handling and CORS headers in error response
Deno.serve(async (req) => {
try {
const { name } = await req.json(); // Fails on OPTIONS (no body)
return new Response(`Hello ${name}`);
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
// Missing CORS headers - browser can't read error!
});
}
});
```
**Correct:**
```typescript
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
Deno.serve(async (req) => {
// Handle preflight OPTIONS request
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
try {
const { name } = await req.json();
return new Response(JSON.stringify({ message: `Hello ${name}!` }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
});
```
Store shared CORS headers in `_shared/cors.ts` and import across functions.
Reference: [CORS Guide](https://supabase.com/docs/guides/functions/cors)

View File

@@ -0,0 +1,60 @@
---
title: Error Handling Patterns
impact: MEDIUM
impactDescription: Improves reliability and debugging experience
tags: edge-functions, errors, debugging, client-errors
---
## Error Handling Patterns
Handle errors gracefully and return meaningful responses. Include helpful error messages in the response body — never expose stack traces, but `error.message` is fine. Always include CORS headers in error responses.
**Incorrect:**
```typescript
// Exposes internal details, wrong status code, missing CORS
Deno.serve(async (req) => {
try {
const data = await riskyOperation();
return Response.json(data);
} catch (error) {
return Response.json({
error: error.message,
stack: error.stack, // Leaks internal details!
}); // Default 200 status for errors!
}
});
```
**Correct:**
```typescript
import { corsHeaders } from "../_shared/cors.ts";
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
try {
const data = await riskyOperation();
return Response.json(data, {
headers: corsHeaders,
});
} catch (error) {
console.error("Internal error:", error); // Log internally
return Response.json(
{ error: error.message },
{
status: 500,
headers: corsHeaders,
}
);
}
});
```
Use appropriate status codes: 400 for validation errors, 401 for unauthorized, 404 for not found, 500 for server errors.
Reference: [Error Handling](https://supabase.com/docs/guides/functions/error-handling)

View File

@@ -0,0 +1,48 @@
---
title: Routing and Multi-Route Functions
impact: MEDIUM-HIGH
impactDescription: Reduces cold starts and simplifies API architecture
tags: edge-functions, routing, hono, url-pattern
---
## Routing and Multi-Route Functions
Handle multiple routes in a single Edge Function to minimize cold starts. Use Hono framework for clean routing. Remember to set basePath matching the function name.
**Incorrect:**
```typescript
// Many separate functions = many cold starts
// supabase/functions/create-user/index.ts
// supabase/functions/get-user/index.ts
// supabase/functions/update-user/index.ts
// Also wrong: missing basePath causes route mismatches
import { Hono } from "jsr:@hono/hono";
const app = new Hono();
app.get("/users", handler); // Won't match /functions/v1/api/users
```
**Correct:**
```typescript
import { Hono } from "jsr:@hono/hono";
import { cors } from "jsr:@hono/hono/cors";
// Single function handling all user operations
const app = new Hono().basePath("/api"); // Must match function name!
app.use("*", cors());
app.get("/users", (c) => c.json({ users: [] }));
app.get("/users/:id", (c) => c.json({ id: c.req.param("id") }));
app.post("/users", async (c) => c.json(await c.req.json(), 201));
app.delete("/users/:id", (c) => c.json({ deleted: c.req.param("id") }));
app.all("*", (c) => c.json({ error: "Not found" }, 404));
Deno.serve(app.fetch);
```
Invoke as `https://PROJECT.supabase.co/functions/v1/api/users/123`.
Reference: [Routing Guide](https://supabase.com/docs/guides/functions/routing)