Skip to main content

Setup and Connection

Get Started​

  1. Create a Supabase project
  2. Create a table
  3. Install supabase-js
  4. Add environment variables
  5. Create the client
  6. Check the connection

Creating a Supabase Project​

  1. Sign up at supabase.com and click New project
  2. Choose a name, a database password (save it somewhere safe), and a region close to your users
  3. Wait a minute or two while the database is created

When it is ready, open Project Settings > API Keys and copy:

  • Project URL, for example https://abcdefghijkl.supabase.co
  • Secret key, which starts with sb_secret_

Creating a Table​

Open the SQL Editor in the dashboard and run:

SQL Editor
create table books (
id bigint generated always as identity primary key,
name text not null unique,
author text not null,
price numeric(10, 2) not null check (price > 0),
stock integer,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);

alter table books enable row level security;

This is the same Book table we built with TypeORM, written as SQL. The check (price > 0) rule is enforced by the database, so no client can ever save a negative price.

Why enable Row Level Security?

With RLS enabled and no policies, nobody can read or write the table using the publishable key. Our Express server uses the secret key, which bypasses RLS, so it still has access. This is a safe default: the table is closed to the public, and only your server can reach it. We will write real policies in the Auth doc.

Installing supabase-js​

mkdir express-with-supabase
cd express-with-supabase
npm init -y
npm install express @supabase/supabase-js dotenv
npm install -D nodemon

Add "type": "module" to package.json so we can use import syntax:

package.json
{
"name": "express-with-supabase",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
}

Environment Variables​

.env
SUPABASE_URL=https://abcdefghijkl.supabase.co
SUPABASE_SECRET_KEY=sb_secret_xxxxxxxxxxxxxxxx
SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxxxxxxxxxxxxxxx

Add .env to .gitignore straight away.

Creating the Client​

Create the client once and import it wherever you need it, the same way we export the TypeORM DataSource.

config/supabase.js
import "dotenv/config";
import { createClient } from "@supabase/supabase-js";

const { SUPABASE_URL, SUPABASE_SECRET_KEY } = process.env;

if (!SUPABASE_URL || !SUPABASE_SECRET_KEY) {
throw new Error("SUPABASE_URL and SUPABASE_SECRET_KEY must be set");
}

export const supabase = createClient(SUPABASE_URL, SUPABASE_SECRET_KEY, {
auth: { persistSession: false, autoRefreshToken: false },
});

A few things to notice:

  • Fail fast: if a variable is missing, the app crashes on startup with a clear message, instead of failing on the first request with a confusing error.
  • persistSession: false: on a server there is no browser storage, and we never want one user's session to stick to a shared client.
  • Secret key: this client bypasses RLS. Only use it in server code.

Checking the Connection​

supabase-js talks to Supabase over HTTP, so there is no connect() step like mongoose.connect() or AppDataSource.initialize(). To fail fast anyway, run a cheap query before starting the server:

index.js
import express from "express";
import { supabase } from "./config/supabase.js";

const app = express();
app.use(express.json());

const { error } = await supabase.from("books").select("id").limit(1);

if (error) {
console.error("Supabase connection failed:", error.message);
process.exit(1);
}

console.log("Supabase Connected ~");
app.listen(9999, () => console.log("Server running on port 9999"));

Using TypeORM with Supabase​

A Supabase database is a normal PostgreSQL database, so everything from the PostgreSQL with TypeORM section works with it. You only change the connection settings.

In the dashboard, click Connect and copy a connection string. Use the Session pooler string for a long-running Express server (it works on IPv4 networks too):

.env
DATABASE_URL=postgresql://postgres.abcdefghijkl:your-db-password@aws-0-eu-central-1.pooler.supabase.com:5432/postgres

Supabase requires SSL. Download the CA certificate from Database Settings > SSL Configuration and save it as supabase-ca.crt in the project root, so the connection is encrypted and the server's identity is verified:

src/data-source.ts
import "reflect-metadata";
import { readFileSync } from "node:fs";
import { DataSource } from "typeorm";
import { Book } from "./entities/Book";

export const AppDataSource = new DataSource({
type: "postgres",
url: process.env.DATABASE_URL,
ssl: { ca: readFileSync("supabase-ca.crt", "utf8") },
synchronize: false,
entities: [Book],
migrations: ["src/migrations/*.ts"],
});
danger

You will often see ssl: { rejectUnauthorized: false } in tutorials. It turns off certificate checks, which lets an attacker on the network pretend to be your database. Use the CA certificate instead.

caution

Keep synchronize: false when pointing TypeORM at Supabase. Supabase creates its own schemas (auth, storage, and others), and you want schema changes to go through migrations, not happen automatically on every restart.

Conclusion​

In this doc, we created a Supabase project and a books table, connected to it from Express with supabase-js, and saw how to point TypeORM at the same database. In the next doc, we will run CRUD queries with supabase-js.