Prerequisites
Before you begin, make sure you have:
Install Supabase CLI
brew install supabase/tap/supabase
Verify installation:
Create a Function
Initialize Your Project
If you haven’t already, initialize Supabase in your project:
This creates a supabase directory with the following structure:
supabase/
├── config.toml
└── functions/
Create Your First Function
Create a new function called hello-world:
supabase functions new hello-world
This creates:
supabase/functions/
└── hello-world/
└── index.ts
Write Function Code
Edit supabase/functions/hello-world/index.ts:
Deno.serve(async (req) => {
const { name } = await req.json()
const data = {
message: `Hello ${name}!`,
timestamp: new Date().toISOString()
}
return new Response(
JSON.stringify(data),
{ headers: { 'Content-Type': 'application/json' } }
)
})
Test Locally
Start Supabase Services
Start the local Supabase stack (requires Docker):
This starts:
- PostgreSQL database
- Auth server
- Realtime server
- Storage server
- Edge Functions runtime
Serve Your Function
Start the function locally:
supabase functions serve hello-world
You should see:
Serving hello-world at http://localhost:54321/functions/v1/hello-world
Test with curl
In another terminal, invoke the function:
curl -L -X POST 'http://localhost:54321/functions/v1/hello-world' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
-H 'Content-Type: application/json' \
-d '{"name":"Functions"}'
Response:
{
"message": "Hello Functions!",
"timestamp": "2024-03-04T10:30:00.000Z"
}
Test with JavaScript
Create a test client:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'http://localhost:54321',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs'
)
const { data, error } = await supabase.functions.invoke('hello-world', {
body: { name: 'World' }
})
if (error) {
console.error('Error:', error)
} else {
console.log('Response:', data)
}
Deploy to Production
Login to Supabase
Authenticate with your Supabase account:
This opens a browser to generate an access token.
Link Your Project
Link to your Supabase project:
supabase link --project-ref your-project-ref
Find your project ref in the Supabase Dashboard under Settings > General.
Deploy the Function
Deploy your function:
supabase functions deploy hello-world
Output:
Deployed function hello-world in 2.3s
https://your-project.supabase.co/functions/v1/hello-world
Test Production Function
Invoke the deployed function:
curl -L -X POST 'https://your-project.supabase.co/functions/v1/hello-world' \
-H 'Authorization: Bearer YOUR_ANON_KEY' \
-H 'Content-Type: application/json' \
-d '{"name":"Production"}'
Replace YOUR_ANON_KEY with your project’s anon key from Settings > API in the Dashboard.
Add Business Logic
Connect to Database
Access your Supabase database from the function:
import { createClient } from 'npm:supabase-js@2'
Deno.serve(async (req) => {
try {
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{
global: {
headers: { Authorization: req.headers.get('Authorization')! }
}
}
)
// Query the database
const { data, error } = await supabaseClient
.from('users')
.select('*')
.limit(10)
if (error) throw error
return new Response(
JSON.stringify({ users: data }),
{ headers: { 'Content-Type': 'application/json' } }
)
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
)
}
})
Add Authentication
Enforce authentication in your function:
import { createClient } from 'npm:supabase-js@2'
Deno.serve(async (req) => {
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{
global: {
headers: { Authorization: req.headers.get('Authorization')! }
}
}
)
// Get authenticated user
const { data: { user }, error: authError } = await supabaseClient.auth.getUser()
if (authError || !user) {
return new Response(
JSON.stringify({ error: 'Unauthorized' }),
{
status: 401,
headers: { 'Content-Type': 'application/json' }
}
)
}
// User is authenticated
return new Response(
JSON.stringify({
message: `Hello ${user.email}!`,
userId: user.id
}),
{ headers: { 'Content-Type': 'application/json' } }
)
})
Handle CORS
Enable CORS for browser requests:
import { corsHeaders } from 'jsr:@supabase/supabase-js@2/cors'
Deno.serve(async (req) => {
// Handle CORS preflight
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'
}
}
)
}
})
Real-World Example: Send Email
Create a function to send emails with Resend:
Create the Function
supabase functions new send-email
Write the Code
// supabase/functions/send-email/index.ts
const RESEND_API_KEY = Deno.env.get('RESEND_API_KEY')
Deno.serve(async (req) => {
try {
const { to, subject, html } = await req.json()
const res = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${RESEND_API_KEY}`
},
body: JSON.stringify({
from: '[email protected]',
to,
subject,
html
})
})
const data = await res.json()
return new Response(
JSON.stringify(data),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
)
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
)
}
})
Set Environment Variable
supabase secrets set RESEND_API_KEY=your_resend_api_key
Deploy
supabase functions deploy send-email
Invoke
const { data, error } = await supabase.functions.invoke('send-email', {
body: {
to: '[email protected]',
subject: 'Welcome!',
html: '<h1>Welcome to our app!</h1>'
}
})
Development Workflow
Watch Mode
Auto-reload on file changes:
supabase functions serve hello-world --no-verify-jwt
The --no-verify-jwt flag skips JWT verification for easier local testing.
Use Environment Variables Locally
Create .env.local:
# supabase/.env.local
RESEND_API_KEY=your_api_key
OPENAI_API_KEY=your_openai_key
Serve with environment variables:
supabase functions serve --env-file ./supabase/.env.local
Test with Postman
Import this cURL command into Postman:
curl -L -X POST 'http://localhost:54321/functions/v1/hello-world' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
-H 'Content-Type: application/json' \
-d '{"name":"Postman"}'
Next Steps
Deploy Functions
Learn advanced deployment strategies and CI/CD
Environment Variables
Manage secrets and configuration
Debugging
Debug and monitor your functions
Troubleshooting
Function Not Found
If you get a 404 error:
- Check the function name matches the directory name
- Verify deployment:
supabase functions list
- Ensure you’re using the correct URL
CORS Errors
If browser requests fail:
- Add CORS headers to your response
- Handle OPTIONS requests
- Include the Authorization header in CORS config
Authentication Errors
If auth doesn’t work:
- Verify you’re passing the Authorization header
- Check the JWT token is valid
- Use
--no-verify-jwt for local testing
Resources