We will cover briefly:
- Setting up Supabase
- Create an Edge Function using Dart
- Deploying and consuming the endpoint
Setting up Supabase
Before diving into Supabase, let’s provide an introduction to what Supabase is.
Supabase is an open-source alternative to Firebase that provides a set of backend services, including authentication, database, and storage.
Supabase offers some features like
- Auth service: for user authentication.
- Database service: standard PostgreSQL.
- Realtime service: sending messages and states to clients.
- Edge functions: Running server-side TypeScript functions, distributed globally at the edge
- Storage service: Storing and serving files.
Setting up Supabase
Pre-Requisite: Docker (you need to have it installed on your machine)
- We will install
Supabase CLI
— a tool to develop your project locally and deploy it to the Supabase Platform.
npm install supabase --save-dev
Note: We need to prefix each command with
npx
(when installing throughnpm
)
To proceed, we will create a Supabase
project, which is a simple process. However, you must first create an account with Supabase.
Once done, run the below command and initialize your project
npx supabase login
Initialize Supabase to set up the configuration locally using
npx supabase init
Make sure Docker is running. Run the following command to start the Supabase services
npx supabase start
After starting all the Supabase services, you will receive an output showing your local Supabase credentials which include URLs and keys. You can use these credentials in your local project.

You can use the npx supabase stop
command to stop all services and reset your local database.
- After the setup, you can access the local Dashboard by visiting
http://localhost:54323
Additionally, you can use any Postgres client to access the database directly through the URL
postgresql://postgres:postgres@localhost:54322/postgres
Supabase Edge Functions using Dart

Supabase Edge Functions is a serverless computing platform that allows you to run custom code on the edge. These functions run on the edge nodes, which are located close to your users and can provide faster response times.
With Supabase Edge Functions, you can build and deploy functions using TypeScript and JavaScript. Additionally, Edge Functions can interact with other Supabase services, including the database, authentication, and storage services.
Edge Functions are developed using Deno

Dart Edge is a project that focuses on executing Dart code on Edge functions for platforms like Cloudflare Workers
, Vercel Edge Functions
, and Supabase Edge Functions
To get started let’s first install the edge
CLI
dart pub global activate edge
and create a project using
edge new supabase_functions <name_of_your_project>

This is the basic structure of the project which we get from running the above command and inside the main.dart
we get a simple Supabase Edge function

For running the edge functions locally, we do the following
npx supabase start # start the supabase stack
npx supabase functions serve # start the Functions watcher
Architecture Overview
We will modify our edge functions to call the YouTube APIs from inside them. This is how our architecture looks like:

- On the left-hand side, we have our client which is our website.
- The website loads for the first time and calls our edge function
VideoFetcher
- This function fetches the data from our Postgres data hosted in the Supabase account
- The response is cached inside the browser’s local storage and the subsequent requests are served from the browser’s cache
- On the right-hand side, we have another edge function
VideoSaver
- This function runs on demand and internally calls the YouTube API.
- The response is transformed as per our
video_items
(more info below) schema and saved inside Postgres.
We will be creating two endpoints — VideoFetcher
and VideoSaver

VideoFetcher Edge Function
In this edge function, we simply query our video_items
table which is present inside our Postgres database. The result is returned (in the form of JSON) back to the client.
This is the schema for the video_items

To fetch data from a PostgreSQL table in a Supabase Edge function, we can use the Supabase Dart client library to query the database. This client is present inside the package supabase
import 'package:supabase/supabase.dart';
final supabase = SupabaseClient(
'https://your-project-url.supabase.co',
'your-anon-key',
httpClient: EdgeHttpClient(),
);
We need to import the library and initialize a client instance with our Supabase credentials. Note, since Supabase functions are developed using Deno
which allows us to retrieve values as
final supabaseUrl = Deno.env.get('SUPABASE_URL');
final supabaseServiceRole = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
We pass the
httpClient
to the Supabase client asEdgeHttpClient
, which is available in thesupabase_functions
library.
Next, we can use the select
method of the client instance to execute a SQL query against the PostgreSQL database.
For example, the following code will fetch all rows from a video_items
table:
const corsHeaders = {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
};
final data = await supabase.from('video_items').select();
return Response.json(
data,
headers: edge.Headers(corsHeaders),
);
The function returns a JSON
a response along with the required CORS headers, which are necessary when calling the endpoint from a client
Tip: To use CORS headers, you must import the edge_runtime package and access the Headers parameter.
Deploying and Testing
We run the following commands to compile and deploy our function on Supabase Edge functions.
# Builds our supabase function
edge build supabase_functions
# Deploys supabase function
npx supabase functions deploy dart_edge --project-ref <PROJECT> --no-verify-jwt
The --no-verify-jwt
flag is used to skip the verification of JSON Web Tokens (JWTs). By default, when a user logs in or signs up with Supabase authentication, a JWT is returned to the client and is used to authenticate future requests. The server verifies the JWT before processing the request.
However, using the --no-verify-jwt
flag means that the server will not verify the JWT.
Tip: Don’t use
--no-verify-jwt
when deploying in production
We can test our function by using the curl
as
curl --request POST 'https://<project>.functions.supabase.co/dart_edge' \
--header 'Content-Type: application/json'
and this gives us the response from the
video_items
table 🎉🎉
VideoSaver Edge Function
In this edge function, we will access the YouTube APIs and store the response in our table video_items
But before doing that, we need

Tip: To avoid receiving a high monthly bill for cloud services: you can limit the amount of usage allowed for the APIs.

Going through the API documentation, it appears that the Search
feature is the most important one we need. Within this feature, there is a method called list
which is described in the documentation.
Returns a collection of search results that match the query parameters specified in the API request. By default, a search result set identifies matching
video
,channel
, andplaylist
resources, but you can also configure queries to only retrieve a specific type of resource.
The response from the above method looks like this
{
"kind": "youtube#searchResult",
"etag": etag,
"id": {
"kind": string,
"videoId": string,
"channelId": string,
"playlistId": string
},
"snippet": {
"publishedAt": datetime,
"channelId": string,
"title": string,
"description": string,
"thumbnails": {},
"channelTitle": string,
"liveBroadcastContent": string
}
}
Note: The parameter definitions are described here
One of the important parameters above is the
videoId
: Its value will contain the ID that YouTube uses to uniquely identify a video that matches the search query.
Calling YouTube API
In order to interact with the Google services, authentication and Service account credentials are required. To acquire these credentials, our initial step is to produce a key within our cloud project.
Head over to the IAM and admin -> Service accounts -> Add KEY -> Create new key

This will generate a key and download it to your local machine. The values contained within this key will be necessary in order to obtain the Service account credentials.
Inside our edge function, we import the googleapis
and googleapis_auth
# Generated Dart libraries for accessing Google APIs.
dart pub get googleapis
# Provides support for obtaining OAuth2 credentials to access Google APIs
dart pub get googleapis_auth
- Next, we create a new file called
video_fetcher.dart
and import the above packages. - We create a function called
videoFetcher
which will call the YouTube APIs
Future<List<SearchResult>?> videoFetcher() async {
const channelId = '<CHANNEL_ID';
final credentials = ServiceAccountCredentials.fromJson({
"private_key_id": "<VALUE>",
"private_key": "<VALUE>",
"client_email": "<VALUE>",
"client_id": "<VALUE>",
'type': 'service_account',
});
final client = await clientViaServiceAccount(credentials, [
'https://www.googleapis.com/auth/youtube.readonly',
]);
final youtube = YouTubeApi(client);
final searchResult = await youtube.search.list(
['id,snippet'],
channelId: channelId,
maxResults: 20,
order: 'date',
pageToken: nextPageToken.isNotEmpty ? nextPageToken : null,
type: ['video'],
eventType: 'none',
);
client.close();
return searchResult.items!;
}
- To start with, we need to obtain the
channel ID
for the specific YouTube channel (a quick Google search will help) from which we desire to retrieve videos. - Next, we obtain the service account credentials utilizing the parameters that were obtained from the previously downloaded file.
- To authorize the API client with the Service account credentials, we must obtain OAuth2 credentials. This is achieved by calling the function
clientViaServiceAccount
which takes two parameters: the necessary Service account credentials and a list of scopes. In this particular scenario, we will be utilizing theyoutube.readonly
scope. Additional information regarding scopes can be found here - We create a
YouTube API
a client that provides access to YouTube data, such as videos, playlists, and channels. - We are interested in calling the
Search
feature which contains a method calledlist
giving us access to the matchingvideo
resources
Note: The parameters mentioned above were adjusted to suit our specific use case. You have the option to modify them to fit your own requirements. As an example, we set the
maxResults
parameter to20
- To conclude, we close both the BrowserOAuth2Flow object and the HTTP Client that it’s utilizing by invoking the
close
method. Once this is done, any subsequent calls toclientViaUserConsent
objects will fail. - And we return the search results back to the calling function
Invoking the VideoFetcher
Inside our dart_edge/video_cron
endpoint we create a method called handleRequest
which will invoke the above created videoFetcher
function
final videoData = await handleRequest();
Future<List<SearchResult>?> handleRequest() async {
final videoResponse = await http.runWithClient(() async {
final data = await videoFetcher();
return data;
}, () => EdgeHttpClient());
return videoResponse;
}
Within the function, we first call http.runWithClient
which is a method that facilitates making requests with an HTTP client
Since we need to make an HTTP request, we import the package HTTP
dart pub add http
We call in our function videoFetcher
and the results are sent back to the callee.
case '/dart_edge/video_cron':
final videoData = await handleRequest();
for (var video in videoData!) {
final videoId = video.id?.videoId;
final isVideoPresent = await doesRowExist(supabase, videoId!);
if (!isVideoPresent) {
await supabase.from('video_items').upsert(
{
'channelId': video.snippet?.channelId,
'title': video.snippet?.title,
'description': video.snippet?.description,
'channelTitle': video.snippet?.channelTitle,
'etag': video.etag,
'videoId': videoId,
},
);
}
}
final data = {
'data': 'processed',
};
return Response.json(
data,
headers: edge.Headers(corsHeaders),
status: HttpStatus.accepted,
);
The videoData
received is in the form of a List of search results. We loop over each item and then get its videoId

The next step involves the creation of a function doesRowExist
which verifies if a row with a specific condition exists in the table. In this case, we are checking whether a row with the videoId
exists, as it is a unique identifier for every video result. We can use the eq
operator which is called with the videoId as its argument
Future<bool> doesRowExist(SupabaseClient supabase, String videoId) async {
final res = await supabase
.from('video_items')
.select()
.eq('videoId', videoId)
.single();
final id = res['videoId'] as String;
return id == videoId;
}
In case the video does not exist, we proceed to insert the data such as channelId
, title
, description
, etc . inside our table with the help of supabase client instances upsert
method.
upsert
method is used to insert a new record or update an existing record in a table. It is a combination ofinsert
andupdate
operations. If a record with the specified primary key already exists in the table, it is updated, otherwise a new record is inserted.
After adding the video, we create a data object with a processed
key-value pair and return it as a JSON response with CORS headers.
The status code of the response is set to
HttpStatus.accepted
, which means the request has been accepted and will be processed later.
Testing the function
We can test our function by using the curl
as
curl --request POST 'https://<project>.functions.supabase.co/dart_edge/video_cron' \
--header 'Content-Type: application/json'
and this saves the latest 20 videos from the Supabase YT channel inside the
video_items
table 🎉🎉
Integrating with client
We create a react app and integrate our dart edge endpoints inside it. And the final result (SupaVidFetcher
) looks like this.
The client request first hits the dart edge endpoint and then the result is cached in the local storage of the browser. The user can also search for videos by entering text into the search bar.
