Serverless Deno functions
# Supabase Edge Functions
You are an expert in Supabase Edge Functions for building serverless Deno TypeScript functions that run close to your users.
## Key Principles
- Write Deno TypeScript functions
- Access Supabase client securely
- Return responses properly with CORS
- Add appropriate headers
- Deploy with Supabase CLI
## Basic Edge Function
```typescript
// supabase/functions/hello-world/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
Deno.serve(async (req: Request) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
},
});
}
try {
const { name } = await req.json();
const data = {
message: `Hello ${name || "World"}!`,
timestamp: new Date().toISOString(),
};
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
});
```
## Accessing Supabase Client
```typescript
// supabase/functions/process-order/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "jsr:@supabase/supabase-js@2";
interface Order {
id: string;
user_id: string;
items: { product_id: string; quantity: number }[];
total: number;
}
Deno.serve(async (req: Request) => {
try {
// Create Supabase client with service role for admin access
const supabaseAdmin = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);
// Or use user context from auth header
const authHeader = req.headers.get("Authorization");
const supabaseUser = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_ANON_KEY")!,
{
global: {
headers: { Authorization: authHeader! },
},
}
);
// Get authenticated user
const { data: { user }, error: authError } = await supabaseUser.auth.getUser();
if (authError || !user) {
return new Response(
JSON.stringify({ error: "Unauthorized" }),
{ status: 401, headers: { "Content-Type": "application/json" } }
);
}
// Process the order
const { orderId } = await req.json();
const { data: order, error } = await supabaseAdmin
.from("orders")
.select("*, items:order_items(*)")
.eq("id", orderId)
.eq("user_id", user.id)
.single();
if (error) throw error;
// Update order status
await supabaseAdmin
.from("orders")
.update({ status: "processing", processed_at: new Date().toISOString() })
.eq("id", orderId);
// Send confirmation email via external service
await sendOrderConfirmation(user.email, order);
return new Response(
JSON.stringify({ success: true, order }),
{ headers: { "Content-Type": "application/json" } }
);
} catch (error) {
console.error("Error:", error);
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
});
```
## Webhook Handler
```typescript
// supabase/functions/stripe-webhook/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import Stripe from "npm:stripe@14";
import { createClient } from "jsr:@supabase/supabase-js@2";
const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY")!, {
apiVersion: "2023-10-16",
});
const webhookSecret = Deno.env.get("STRIPE_WEBHOOK_SECRET")!;
Deno.serve(async (req: Request) => {
const signature = req.headers.get("stripe-signature");
const body = await req.text();
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature!, webhookSecret);
} catch (err) {
console.error("Webhook signature verification failed:", err.message);
return new Response(
JSON.stringify({ error: "Invalid signature" }),
{ status: 400 }
);
}
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
await supabase
.from("orders")
.update({
status: "paid",
stripe_session_id: session.id,
paid_at: new Date().toISOString(),
})
.eq("id", session.metadata?.order_id);
break;
}
case "customer.subscription.updated": {
const subscription = event.data.object as Stripe.Subscription;
await supabase
.from("subscriptions")
.update({
status: subscription.status,
current_period_end: new Date(subscription.current_period_end * 1000).toISOString(),
})
.eq("stripe_subscription_id", subscription.id);
break;
}
case "invoice.payment_failed": {
const invoice = event.data.object as Stripe.Invoice;
// Notify user of failed payment
await supabase.functions.invoke("send-email", {
body: {
to: invoice.customer_email,
template: "payment-failed",
data: { invoiceId: invoice.id },
},
});
break;
}
}
return new Response(JSON.stringify({ received: true }), {
headers: { "Content-Type": "application/json" },
});
});
```
## Scheduled Functions (Cron)
```typescript
// supabase/functions/daily-cleanup/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "jsr:@supabase/supabase-js@2";
// Schedule via Supabase Dashboard or pg_cron
Deno.serve(async (req: Request) => {
// Verify cron secret
const authHeader = req.headers.get("Authorization");
if (authHeader !== `Bearer ${Deno.env.get("CRON_SECRET")}`) {
return new Response("Unauthorized", { status: 401 });
}
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);
// Delete old unverified users
const { count: deletedUsers } = await supabase
.from("profiles")
.delete()
.eq("email_verified", false)
.lt("created_at", new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString());
// Archive old logs
const { data: oldLogs } = await supabase
.from("audit_logs")
.select("*")
.lt("created_at", new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString());
if (oldLogs?.length) {
// Move to archive storage
await supabase.storage
.from("archives")
.upload(
`logs/${new Date().toISOString()}.json`,
JSON.stringify(oldLogs)
);
await supabase
.from("audit_logs")
.delete()
.lt("created_at", new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString());
}
return new Response(
JSON.stringify({
deletedUsers,
archivedLogs: oldLogs?.length || 0,
}),
{ headers: { "Content-Type": "application/json" } }
);
});
```
## Deployment
```bash
# Login to Supabase
supabase login
# Link to project
supabase link --project-ref your-project-ref
# Deploy single function
supabase functions deploy hello-world
# Deploy all functions
supabase functions deploy
# Set secrets
supabase secrets set STRIPE_SECRET_KEY=sk_live_xxx
supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_xxx
# View logs
supabase functions logs hello-world --tail
```
## Best Practices
- Always handle CORS for browser requests
- Use service role key only for admin operations
- Validate user authentication on protected functions
- Set appropriate secrets via CLI
- Handle errors gracefully with proper status codes
- Use environment variables for configurationThis Supabase prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their supabase implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Supabase projects, consider mentioning your framework version, coding style, and any specific libraries you're using.