Setup and Connection
Get Started
- Create a Supabase project
- Create a table
- Install supabase-js
- Add environment variables
- Create the client
- Check the connection
Creating a Supabase Project
- Sign up at supabase.com and click New project
- Choose a name, a database password (save it somewhere safe), and a region close to your users
- 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:
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.
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
- Yarn
- pnpm
- Bun
npm install express @supabase/supabase-js dotenv
npm install -D nodemon
yarn add express @supabase/supabase-js dotenv
yarn add --dev nodemon
pnpm add express @supabase/supabase-js dotenv
pnpm add -D nodemon
bun add express @supabase/supabase-js dotenv
bun add --dev nodemon
Add "type": "module" to package.json so we can use import syntax:
{
"name": "express-with-supabase",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
}
Environment Variables
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.
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:
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):
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:
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"],
});
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.
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.