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-devNote: 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 loginInitialize Supabase to set up the configuration locally using
npx supabase initMake sure Docker is running. Run the following command to start the Supabase services
npx supabase startAfter 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:54323Additionally, you can use any Postgres client to access the database directly through the URL
postgresql://postgres:postgres@localhost:54322/postgresSupabase 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 edgeand 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 watcherArchitecture 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
httpClientto the Supabase client asEdgeHttpClient, which is available in thesupabase_functionslibrary.
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-jwtThe --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-jwtwhen 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_itemstable 🎉🎉
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 Searchfeature is the most important one we need. Within this feature, there is a method called listwhich 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, andplaylistresources, 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.dartand import the above packages. - We create a function called
videoFetcherwhich 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 IDfor 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
clientViaServiceAccountwhich takes two parameters: the necessary Service account credentials and a list of scopes. In this particular scenario, we will be utilizing theyoutube.readonlyscope. Additional information regarding scopes can be found here - We create a
YouTube APIa client that provides access to YouTube data, such as videos, playlists, and channels. - We are interested in calling the
Searchfeature which contains a method calledlistgiving us access to the matchingvideoresources
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
maxResultsparameter to20
- To conclude, we close both the BrowserOAuth2Flow object and the HTTP Client that it’s utilizing by invoking the
closemethod. Once this is done, any subsequent calls toclientViaUserConsentobjects 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.runWithClientwhich 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 httpWe call in our function videoFetcherand 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 doesRowExistwhich 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.
upsertmethod is used to insert a new record or update an existing record in a table. It is a combination ofinsertandupdateoperations. 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_itemstable 🎉🎉
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.

