Supabase for Persistence and Server to Client Push in Vanilla HTML/JavaScript web application–a step by step guide

Lucas Jellema

Supabase provides a cloud based backend service to web applications. That means that any (static) web application can use Supabase for user authentication and data persistence. Supabase provides a server to client) push notification service and it has server side serverless functions that can for example handle web hooks (triggers from many different internet services). This article describes my first tentative steps with Supabase and a simple web application consisting only of HTML and JavaScript.

image

My objective: create a web application using vanilla HTML & JavaScript and connect it to a Supabase backend to provide persistence for the application’s data as well as real time updates pushed to the application in case of database updates. I will first create a simple Node application that connects, inserts, queries and subscribes [to notifications]. Once I have the hang of that, I will create the client side that does similar things.

A colleague recently alerted me regarding Supabase: a service that provides a backend for webapplications with facilities such as user management and authentication, data persistence, real time data push and file storage, serverless (edge) functions. Data persistence is provided by a managed PostgreSQL database. Supabase allows us to get started in their free tier – and also to run the platform locally – independent from the cloud backend. Supabase is positioned as an alternative to Firebase. However, I do not know Firebase well enough to make any comparison.

I have read some articles and browsed the website. I have not yet tried out Supabase and I do not yet know how to get going. I will find out soon enough – and share my ride in this article.

Supabase Project

Supabase organizes resources in projects. Each project has its own database instance, its own set of database policies, users and authentication rules. To get going with Supabase, signing up is step one and the creation of a project comes immediately after that.

Go to https://app.supabase.com/

image
Sign in with GitHub
Authorize Supabase on GitHub

image
Click on New Project

image

Provide name for the project and password for database access.

image

Choose region and select plan (Free might be a good start ;-))

Click on Create new project

After the project has been created, go to SQL Editor in Database area

image

Click on Create Table


Edit the proposed table definition in a small way – change its name to app_state.

imageimage

Click on Run to have the table created in the database. The REST API is now generated as well.

image

When the table has been created successfully, switch to the API tab. Here, instructions are provided for interacting with the API that was generated for the table.

image

image

Now the backend is created, it is time to create an application to interact with it.

Local Development – Node application

To get a feel for interaction with the REST API, I first created a simple Node application that uses the Supabase Javascript SDK for interacting with the backend. The application makes a connection, reads records from the APP_STATE table and creates new records in that table. In a second iteration, I will add a subscription to changes in the APP_STATE table and any change is reported as push notification to the Node application.

image

Note: all code shown in this article is available from a GitHub Repo: https://github.com/lucasjellema/supabase-lab/tree/main/lab1

Create a new folder in my local development environment –  lucas-lab. On the command line, execute

npm init

accept all defaults in the dialog that follows.

Now execute:

npm install –save @supabase/supabase-js

image

Create file index.js. Copy the following code

const { createClient } = require(‘@supabase/supabase-js’)

const supabaseUrl = process.env.SUPABASE_URL

const supabaseKey = process.env.SUPABASE_KEY

const supabase = createClient(supabaseUrl, supabaseKey)

const main = async () => {

let { data, error } = await supabase

      .from(‘app_state’)

      .select(‘name, data’)

      .range(0,10)

if (error) {

      console.error(error)

return

    }

    console.log(data)

  }

  main()

This code simply queries the first 10 records from table APP_STATE in the Supabase project’s backend and writes the data retrieved to the console.

Set environment variable SUPABASE_KEY and SUPABASE_URL using the values available from
Settings – API – Project API Keys

export SUPABASE_KEY=”<key value>”

export SUPABASE_URL=”<url value>”

or under Windows Powershell:

$Env:SUPABASE_KEY = ‘<key value>’

$Env:SUPABASE_URL = ‘<url value>’

(of course for code that is only run in my local environment, I might as well set the value of `const supabaseKey` and `const supabaseUrl` directly in the source code).

Run this application with `node index.js` and the output is perhaps underwhelming, but meaningful.

image

The connection to the Supabase API is established, the data from the table is retrieved. It just is not very much right now.

Next, I have added a record to table APP_STATE, in the client UI using the table editor.

image

I run the application again, with a little more effect:

image

I add this following snippet to the application. This constitutes a call to the supabase backend to create a new record in table APP_STATE, with column name set to “New List” and data set to the JSON representation of the object with property *shop*.
“`

// add as first entry in function main
let { dataI, errorI } = await supabase

    .from(‘app_state’)

    .insert([

      { name: ‘New List’, data: {shop:{name:”AH”, total:267.12}} }

    ])
“`

The output of running this code:

image

showing that a new record was retrieved from the table – that was first added trough the insert statement I had just added.

Here is the table editor in the Supabase web console (also with the new record):

image

Realtime – Server to Client Push notifications for Table Changes

Client applications can be notified by Supabase of any changes in the data in selected tables. We need to do two things to make this work:

  1. make sure in the Supabase backend that changes to the relevant tables are included in the replication mechanism
  2. subscribe a function in the client application to the relevant changes in the designated table(s)

image

1. Add table to replication

Click on database in the sidebar. Then on Replication; Replication most likely is already enabled.  If it is not, then enable it now. To add table APP_STATE to the set of tables whose changes are published, click on the icon under header *Source* that indicates the number of tables currently being replicated.

image

Select table APP_STATE from the available tables.

image

2. Subscribe client to Realtime Data Changes

Subscribing the application to Real Time changes in table APP_STATE is done with this snippet, somewhere in function main:

“`
subscription1 = supabase

      .from(‘app_state’)

      .on(‘UPDATE’, (v) => console.log(‘UPDATE on app_state’, v))

      .on(‘INSERT’, (v) => console.log(‘INSERT on app_state’, v))

      .subscribe((change) => console.log(‘app_state changed’, change))

 

    await sleep(30000)

    supabase.removeSubscription(subscription1);
  “`

Function sleep() needs to be added – to pause processing the Node application to give us time to make changes in the Supabase table editor and see that change pushed to the client application:
“`
function sleep(time) {

    return new Promise(resolve => setTimeout(resolve, time));

}
“`

Run the node application. When it starts, you have 30 seconds to make a change in the data in table APP_STATE through the table editor – and see the effect on the console.

image

And the output from the Node application:

image

This is powerful stuff. With a few lines of code, this application can save its state, retrieve state and subscribe to changes in change – manipulated  from any client working against this cloud based backend.

Vanilla HTML & JavaScript Web Application

Even more useful than leveraging Supabase from a server side Node application – where we have easy access to kinds of backend services – is the ability to access Supabase from a web application running in a browser directly.

The picture still show Node – but only to serve the static web application to the browser (this could be nginx or apache httpd just as easily), All interactions with Supabase take place from the client – the JavaScript code in the browser.

image

I have created the Node file web-server.js. It does exactly one thing: serve the file index.html in subdirectory web:

const http = require(‘http’)

const fs = require(‘fs’)

const PORT = 3100

// create an HTTP server that handles HTTP requests; it is handed two parameters: the request and response objects

const server = http.createServer((req, resp) => {

    fs.readFile(“web/index.html”, function (error, page) {

if (error) {

            resp.writeHead(404);

            resp.write(‘Contents you are looking are Not Found’);

        } else {

            resp.writeHead(200, { ‘Content-Type’: ‘text/html’ });

            resp.write(page);

        }

        resp.end();

    })

})

server.listen(PORT);

console.log(`HTTP Server is listening at port ${PORT} for HTTP GET requests`)

The index.html document is the interesting one here. It shows the content of the APP_STATE table in the browser:

image

A short guide through the code:

  1. load the Supabase JavaScript Client (SDK) (this line is similar to the combination of the “@supabase/supabase-js” dependency in package.json and the require(‘@supabase/supabase-js’) in index.js in the Node application
  2. the Supabase Key and Url are not hardcoded ; instead they are learned at runtime from URL query parameters
  3. when the document has loaded in the browser, this function will execute and the Supabase client is initialized and subsequently the data is queried from table APP_STATE
  4. just as in the Node application, the Supabase REST API is called (through the client SDK) to query up to 10 records from the indicated table APP_STATE
  5. a new row is added to the HTML table for every record in the data retrieved from Supabase

Run the Node application web-server.js

image

Then open the web application in a local browser using this URL:

http://localhost:3100/?SUPABASE_URL=bctaasdsaqwfw&SUPABASE_KEY=eyJhqams

the value for query parameter SUPABASE_URL is the first path segment in the URL (not including the leading https:// nor the generic .supabase.co postfix.

The browser opens – I did not claim it would be pretty – and presents a table that contains the data retrieved from the APP_STATE backend table:

image

Live Change Report in Web Application – Realtime Server to Client Push

When a change is made in the APP_STATE table and the browser is refreshed, the latest state of the data is shown. We can do one better – at least one – using the real time server to client notifications.

Add a small snippet of HTML to the index.html page – just under the <table> element:

image

<h3>Latest Change</h3>

<div id=”breakingNews”></div>

<br /><br />

<button id=”refresh” onclick=”refreshData()”>Refresh</button>

Then add function subscribeAppState to the <script> section in index,html and include a call to the function inside the function registered for the DOMContentLoaded event:

image

Save file index.html. Refresh the browser.

The table is still shown, along with a header Latest Change and button labeled Refresh. The console shows a message indicating that subscription has taken place.

image

If I now make a change in the Supabase table editor – for example removing a record:

image

then this change is reported almost immediately in the browser. After pressing button refresh the table is also updated to reflect the current situation in table APP_STATE.

image

Run Web Application on GitHub Pages

The web application is now running locally on my laptop using the Node web server application. That works, but is not the desired situation. Ideally, I need to run nothing in order to access the application and especially in order for you to access my application. Of course in this case I would need to hand out the Supabase Key and Url for the application to be usable to you – but perhaps I will do just that. So how can I make this static web application (one time download of static, unchangeable web resources to the browser is all that is required to run the application) available to anyone?

This is where GitHub Pages comes in. GitHub Pages is designed to host your personal, organization, or project pages from a GitHub repository. That is all I need to make the application accessible.

In order to get this going, I will go to the GitHub Repository Console and enable GitHub Pages – specifying the branch and the directory where publishable assets are located:

image

After saving these changes, the following message appears:

image

It can take a few minutes for the publication to be complete. When it is, I – and you too – will be able to access the application at: https://lucasjellema.github.io/supabase-lab/lab1/web/index.html. Of course the query parameters SUPABASE_KEY and SUPABASE_URL need to be added.

image

Resources

all code shown in this article is available from a GitHub Repo: https://github.com/lucasjellema/supabase-lab/tree/main/lab1

Supabase JS Client Library – https://github.com/supabase/supabase-js and https://supabase.com/docs/reference/javascript/insert

Read the description on how to enable realtime change publication for tables: https://supabase.com/docs/guides/database/replication

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Next Post

Is RHCSA still relevant when you use the cloud?

Last week I passed for my RHCSA exam (EX200) [1]. While I was studying for the exam, I asked myself which parts of this exam are still relevant when you use Linux servers and Linux containers in the cloud and which parts are not. Assumptions In the cloud, we prefer: […]
Man behind laptop with book
%d bloggers like this: