Crossroad Development

two roads crossed to make an X

Expanding a Guide to Using Supabase with Node.js

2023-06-24

So, I have had some experience working with SQL databases, but I haven’t stood up an authentication/authorization app before. There are a lot of moving parts, password hashing, and security measures to sort out to try and bring a bespoke solution to production. I have heard a phrase that kind of sums up this predicament, you either buy it or build it. Since building it seems doable if time wasn’t an issue, buying it seems like a waste for something I couldn’t see more than a thousand or so people using, I split the difference and found Supabase. 

This solution provides row-level-secure auth that can use social or magic link passwords, and file storage that can do photo transformation to speed up page load, all built on a Postgres database with built-in search encryption back-ups and migrations. Not to overstate the point of avoiding technical debt, but even the “small” features packed in this are going to save you a massive amount of time. The security for example, by default tables will be set up with row-level access which allows the user to see only data associated with that user, while this should mitigate most unauthorized access, you might be wondering about SQL injection since it can use SQL to communicate. Normally, one might use a regular expression to sanitize the data, but with the supabase-js API, the statement is prepared and there is no risk to SQL injection according to their GitHub questions. This project is open-source, so the price is looking pretty good, but they also offer a hosted version of the platform which starts free. So I have decided to do a series building up to setting up an authorized user system in a generic Express sever. 

I found a great guide on Medium, by Hesh Ramsis that walks you through the steps you need to take to get your endpoints running. I really appreciated that it was focused on junior devs to build a CRUD app because that is kind of the big user project that a junior might build in school like a to-do app or a Twitter clone. I might try and mock up an e-commerce store for the following projects, just to kind of match Hesh’s example table schema. I do want to note there is a new way of using the Supabase API, that team is moving quickly, so I don’t doubt that would have been the syntax back in February.

const supabase = supabaseClient.createClient({
apiKey: '<API_KEY>',
project: '<PROJECT_ID>'
});

const supabase = createClient(‘<PROJECT_URL’,’<API_KEY>’);

Another two things to keep in mind, that are mentioned in the guide, but bear repeating, make sure you have a package.json that shows that this is a module, and when we get to manipulating the table turn off row-level access. So, in the later projects that setting should be on, which allows direct database calls from the client without having to worry because the client can only access data associated with their account. I prefer not exposing my API key by just using it on the backend, which is interesting because there is an API key that has enhanced permissions. These are just things to consider on the next project, authorization.

So, after getting the node server running, you’ll note there exist all the endpoints needed to create a CRUD app, and you should also have a table set up to start proving the concept. For this, I made the literal bare minimum of an HTML front end, just a form with no styling. And I only built the form for inserting a new row into that test table from the tutorial. I think it would be interesting to build a form or table for each create, read, update, and delete endpoints.  I heard recently that you shouldn’t be afraid to write programs that will never be used in production. This example should get anyone started, I wonder what kind of project this is going to morph into.

<html>


<body>
<form name="insert" action="javascript:create()">
<input name="name" type="text"/><br>
<input name="description" type="text"/><br>
<input name="price" type="text"/><br>
<input type="submit"/>


</form>
<script>


async function create (){
let name = document.forms['insert'].name.value;
let description = document.forms['insert'].description.value;
let price = document.forms['insert'].price.value;
let query = {name: name, description: description, price: price};
const headers = { 'Content-Type': 'application/json' };
const res = await fetch('/products', {
method: 'POST',
headers,
body: JSON.stringify(query)
});
const json = await res.json();
console.log(json)
alert(json.res);
}
</script>
</body>
</html>

Then you just need to change your result method in your app.js to serve the HTML file above. This requires calling the path and URL modules and setting constants to match what would normally be default if this wasn’t structured as a module if I’m not mistaken.

import { fileURLToPath } from 'url';
import path from 'path';

const app = express();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

app.get('/', (req, res) => {
    res.sendFile(__dirname+'/index.html');
});

Comments