# Add a database to a Node.js or Express API with one command

> Give a Node.js or Express API a Postgres database with auth and storage: `npx scribase init` writes .env with the URL and keys, installs supabase-js.

Source: https://scribase.com/frameworks/express

For a server there is no bundle to leak into, so init writes all three values to .env, including the service role key for trusted server work. Node 20.6 and later load the file with --env-file; nothing else to install.

## `npx scribase init` output

```
$ npx scribase init
Backend ready: https://acme-my-api-production-20d198a3.scribase.com
  framework   node
  project     acme/my-api (production)
  env file    .env (SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, SCRIBASE_API_URL, SCRIBASE_ORG, SCRIBASE_PROJECT, SCRIBASE_ENV)
  client      supabase.mjs

Next:
  - import { supabase } from './supabase' and query: await supabase.from('todos').select()
  - create tables with SQL migrations (supabase/migrations/*.sql) and `scribase db push`, or the MCP server
```

## Environment file: .env

- SUPABASE_URL
- SUPABASE_ANON_KEY
- SUPABASE_SERVICE_ROLE_KEY

## Run init

Run it in the project root. If you are not signed in, it prints a link and a code: approve it in the browser (sign up takes one click plus the $1 card check, credited back), and init carries on. No key is ever pasted. It creates the project, writes the env file, adds the env file to .gitignore, installs @supabase/supabase-js and writes a small client module.

```bash
npx scribase init
```

## What it wrote

Without a tsconfig.json the client is supabase.mjs (supabase.ts with one; src/ when you have a src folder).

_.env_
```bash
# Scribase (written by `npx scribase init`)
SUPABASE_URL=https://acme-my-api-production-20d198a3.scribase.com
SUPABASE_ANON_KEY=<publishable key>
SUPABASE_SERVICE_ROLE_KEY=<service role key>
SCRIBASE_API_URL=https://api.scribase.com
SCRIBASE_ORG=acme
SCRIBASE_PROJECT=my-app
SCRIBASE_ENV=production
```

## Create your first table

Open your project in the console, go to Editor, then SQL, and run this. Or ask your coding agent: with the Scribase MCP server connected it applies the same SQL as a migration. Row-level security is on, so the public key can only read what the policy allows.

```sql
create table public.todos (
  id bigint generated always as identity primary key,
  title text not null,
  done boolean not null default false,
  created_at timestamptz not null default now()
);

alter table public.todos enable row level security;

create policy "todos are readable" on public.todos
  for select to anon, authenticated using (true);

insert into public.todos (title) values ('Ship the backend'), ('Tell a friend');
```

## Serve it from a route

Start it with `node --env-file=.env server.mjs`.

_server.mjs_
```js
import express from 'express';
import { supabase } from './supabase.mjs';

const app = express();

app.get('/todos', async (_req, res) => {
  const { data, error } = await supabase.from('todos').select('id, title, done');
  if (error) return res.status(500).json({ error: error.message });
  res.json(data);
});

app.listen(3000);
```

## Act as the signed-in user

To keep row-level security on for a request, create a client per request with the user access token from the Authorization header, instead of using the service role key.

```js
import { createClient } from '@supabase/supabase-js';

function clientFor(req) {
  return createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, {
    global: { headers: { Authorization: req.headers.authorization ?? '' } },
  });
}
```

## Agent prompt

```
Add a Scribase backend to this Node.js / Express app.
1. Run `npx scribase init --yes --json` in the project root. If it returns authorization_pending, show me the link and code, wait for me to approve, then run it again.
2. Create a `todos` table (id, title, done, created_at) with row-level security on and a policy that lets anyone read it; use the Scribase MCP server or a SQL migration, not the service-role key in app code.
3. Add a GET /todos route using the generated client, start the server with node --env-file=.env, and use a per-request client with the caller access token wherever user data is involved.
4. Never print, log or commit the env file; it is already in .gitignore.
```

## FAQ

**Can I connect with a Postgres driver instead?**

Yes. Every project is a real Postgres database; the console shows its connection string. supabase-js is the shortest path because it also covers auth and storage.

**Does it work with Fastify, Hono or NestJS?**

Yes. Detection falls back to Node for anything that is not Next.js, Expo or Vite, and the client is plain supabase-js.