Skip to main content

Source: ocean/docs/patterns/edge-functions.md | ✏️ Edit on GitHub

Edge Functions Pattern Reference

Quick Reference: All Edge Functions use standardized wrapper patterns for authentication, CORS, and error handling.

Wrapper Types

1. createEdgeFunction

Use for: Standard authenticated endpoints

import { createEdgeFunction } from '../_shared/function-wrapper.ts'

export default createEdgeFunction(async (req, context) => {
const { user, supabaseClient, logger } = context

// user.id is guaranteed to exist
// supabaseClient has RLS enabled for this user

return new Response(JSON.stringify({ userId: user.id }))
})

Features:

  • ✅ JWT authentication required
  • ✅ User context available
  • ✅ Supabase client with RLS
  • ✅ Automatic CORS
  • ✅ Error handling with Sentry

2. createPublicFunction

Use for: Public endpoints (no authentication)

import { createPublicFunction } from '../_shared/function-wrapper.ts'

export default createPublicFunction(async (req, context) => {
const { supabaseClient, logger } = context

// No user context - public access
// supabaseClient uses service role (no RLS)

return new Response('Public endpoint')
})

Features:

  • ❌ No authentication required
  • ❌ No user context
  • ✅ Service role Supabase client
  • ✅ Automatic CORS
  • ✅ Error handling with Sentry

3. createStripeFunction

Use for: Stripe API interactions (public or authenticated)

// Public Stripe function (like setup intents)
import { createStripeFunction } from '../_shared/function-wrapper.ts'

export default createStripeFunction(
async (req, context) => {
const { stripe, logger } = context

// Create Stripe resources
const paymentIntent = await stripe.paymentIntents.create({
amount: 2000,
currency: 'usd',
})

return Response.json({ client_secret: paymentIntent.client_secret })
},
{ requireAuth: false }
)

// Authenticated Stripe function (like subscriptions)
export default createStripeFunction(async (req, context) => {
const { user, stripe, supabaseClient } = context

// Both user context AND Stripe SDK available

return Response.json({ success: true })
})

Features:

  • ✅ Stripe SDK initialized
  • ✅ Configurable authentication (requireAuth: true/false)
  • ✅ User context when authenticated
  • ✅ Automatic CORS
  • ✅ Error handling with Sentry

4. createWebhookFunction

Use for: External webhook endpoints

import { createWebhookFunction } from '../_shared/function-wrapper.ts'

export default createWebhookFunction(
async (req, context) => {
const { rawBody, logger } = context

// Signature validation handled automatically
// Raw body available for verification

const event = JSON.parse(rawBody)

return new Response('OK')
},
{
expectedHeader: 'stripe-signature',
webhookSecret: Deno.env.get('STRIPE_WEBHOOK_SECRET')!,
}
)

Features:

  • ✅ Signature validation
  • ✅ Raw body access
  • ✅ Configurable webhook validation
  • ❌ No user context
  • ✅ Automatic CORS
  • ✅ Error handling with Sentry

5. createGraphQLFunction

Use for: GraphQL Yoga servers

import { createGraphQLFunction } from '../_shared/function-wrapper.ts'
import { createYoga } from 'graphql-yoga'

const yoga = createYoga({
schema: typeDefs,
resolvers: resolvers,
})

export default createGraphQLFunction((context) => ({
yoga,
allowedOrigins: ['http://localhost:3000', 'https://ocean.goldfish.io'],
}))

Features:

  • ✅ GraphQL Yoga integration
  • ✅ CORS configuration
  • ✅ Request context passing
  • ✅ Authentication context available in resolvers

Context Objects

Authenticated Context

interface AuthenticatedContext {
user: {
id: string
email?: string
}
supabaseClient: SupabaseClient // RLS enabled
logger: Logger
}

Public Context

interface PublicContext {
supabaseClient: SupabaseClient // Service role
logger: Logger
}

Stripe Context

interface StripeContext {
stripe: Stripe
user?: { id: string; email?: string } // If requireAuth: true
supabaseClient: SupabaseClient
logger: Logger
}

Webhook Context

interface WebhookContext {
rawBody: string
logger: Logger
}

Common Patterns

Database Queries

export default createEdgeFunction(async (req, context) => {
const { user, supabaseClient } = context

// RLS automatically filters for user's data
const { data } = await supabaseClient.from('organizations').select('*').eq('owner_id', user.id)

return Response.json(data)
})

Error Handling

export default createEdgeFunction(async (req, context) => {
const { logger } = context

try {
// Business logic
} catch (error) {
// Automatic Sentry logging via wrapper
logger.error('Operation failed', { error })
throw error // Wrapper handles HTTP error response
}
})

Request Parsing

export default createEdgeFunction(async (req, context) => {
if (req.method !== 'POST') {
return new Response('Method not allowed', { status: 405 })
}

const body = await req.json()

// Validate body...

return Response.json({ success: true })
})

File Organization

supabase/functions/
├── _shared/
│ ├── function-wrapper.ts # All wrapper functions
│ ├── auth.ts # Auth utilities
│ └── stripe-client.ts # Stripe initialization
├── graphql-v2/
│ ├── index.ts # Uses createGraphQLFunction
│ └── setup.ts # GraphQL setup logic
├── stripe-subscription/
│ └── index.ts # Uses createStripeFunction
├── handle-stripe-webhook/
│ └── index.ts # Uses createWebhookFunction
└── provision-tenant-resources/
└── index.ts # Uses createEdgeFunction

Debugging

Test Locally

supabase functions serve stripe-subscription --no-verify-jwt
curl -X POST http://localhost:54321/functions/v1/stripe-subscription

View Logs

supabase functions logs stripe-subscription

Common Issues

CORS Errors: All wrappers handle CORS automatically. If you see CORS errors, check wrapper usage.

Auth Errors:

  • createEdgeFunction requires JWT token in Authorization: Bearer <token> header
  • createPublicFunction never requires auth
  • createStripeFunction requires auth if requireAuth: true

Context Missing: Check wrapper type - each provides different context objects.