Introduction to Supabase Functions
Supabase Functions are a powerful feature that allows developers to create serverless, event-driven APIs with ease. These functions run at the edge, providing low-latency responses and seamless integration with other Supabase services. In this guide, we'll explore how to leverage Supabase Functions to build robust APIs for your applications.
Setting Up Your First Supabase Function
Let's start by creating a simple "Hello, World!" function:
-
Install the Supabase CLI if you haven't already:
npm install -g supabase
-
Initialize a new Supabase project:
supabase init
-
Create a new function:
supabase functions new hello-world
-
Open the newly created
supabase/functions/hello-world/index.ts
file and add the following code:import { serve } from "https://deno.land/std@0.168.0/http/server.ts" serve(async (req) => { const { name } = await req.json() const data = { message: `Hello ${name}!`, } return new Response( JSON.stringify(data), { headers: { "Content-Type": "application/json" } }, ) })
-
Deploy your function:
supabase functions deploy hello-world
Now you have a basic API endpoint that accepts a JSON payload with a name
field and responds with a greeting!
Handling Different HTTP Methods
Supabase Functions can handle various HTTP methods. Let's expand our previous example to support both GET and POST requests:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts" serve(async (req) => { if (req.method === 'GET') { return new Response( JSON.stringify({ message: "Hello, World!" }), { headers: { "Content-Type": "application/json" } }, ) } else if (req.method === 'POST') { const { name } = await req.json() return new Response( JSON.stringify({ message: `Hello, ${name}!` }), { headers: { "Content-Type": "application/json" } }, ) } else { return new Response( JSON.stringify({ error: "Method not allowed" }), { status: 405, headers: { "Content-Type": "application/json" } }, ) } })
This function now handles GET requests with a default greeting and POST requests with a personalized greeting based on the provided name.
Implementing Authentication
Supabase Functions can be easily secured using JWT authentication. Here's how to modify your function to require authentication:
-
First, update your
supabase/config.toml
file to enable JWT verification:[functions] [functions.hello-world] verify_jwt = true
-
Modify your function to use the authenticated user's information:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts" import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' serve(async (req) => { // Create a Supabase client with the Auth context of the logged in user. const supabaseClient = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '', { global: { headers: { Authorization: req.headers.get('Authorization')! } } } ) // Now we can get the session or user object const { data: { user }, } = await supabaseClient.auth.getUser() // And we can run queries in the context of our authenticated user const { data, error } = await supabaseClient.from('users').select('*') return new Response( JSON.stringify({ user, data }), { headers: { 'Content-Type': 'application/json' } }, ) })
This function now requires a valid JWT token and uses it to fetch the authenticated user's information and perform database queries.
Interacting with the Database
Supabase Functions can directly interact with your Supabase database. Let's create a function that fetches and returns a list of items from a hypothetical items
table:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts" import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' serve(async (req) => { const supabaseClient = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_ANON_KEY') ?? '', { global: { headers: { Authorization: req.headers.get('Authorization')! } } } ) const { data, error } = await supabaseClient .from('items') .select('*') .order('created_at', { ascending: false }) .limit(10) if (error) { return new Response( JSON.stringify({ error: error.message }), { status: 500, headers: { 'Content-Type': 'application/json' } }, ) } return new Response( JSON.stringify({ items: data }), { headers: { 'Content-Type': 'application/json' } }, ) })
This function fetches the 10 most recently created items from the items
table and returns them as a JSON response.
Error Handling and Logging
Proper error handling and logging are crucial for maintaining and debugging your APIs. Here's an example of how to implement robust error handling:
import { serve } from "https://deno.land/std@0.168.0/http/server.ts" import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' 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')! } } } ) const { data, error } = await supabaseClient .from('items') .select('*') .order('created_at', { ascending: false }) .limit(10) if (error) throw error return new Response( JSON.stringify({ items: data }), { headers: { 'Content-Type': 'application/json' } }, ) } catch (error) { console.error('Error in function:', error.message) return new Response( JSON.stringify({ error: 'An unexpected error occurred' }), { status: 500, headers: { 'Content-Type': 'application/json' } }, ) } })
This implementation uses a try-catch block to handle any unexpected errors and logs them for debugging purposes.
Conclusion
Supabase Functions provide a powerful and flexible way to build serverless APIs. By leveraging these functions, you can create scalable, secure, and efficient backend services that integrate seamlessly with other Supabase features. As you continue to explore Supabase, you'll discover even more ways to enhance your APIs and build robust applications.