Quickstart: Node.js and Express
Build a notes API with Express that signs users in through Sovaria and runs every query as the calling user, so your row rules apply on the server too.
An Express API with POST /signup, POST /signin, GET /notes and POST /notes. It never holds a key that can bypass your rules: each request runs as the user whose token it carries.
#Before you start
Do steps 1 to 3 of the Quickstart first: create a project, copy its project URL and anon key (Quickstart), and create the notes table with its row rules (Quickstart). Every signed-in user then sees and changes only their own notes, whatever the app code does.
#1. Create the project
mkdir notes-api && cd notes-api
npm init -y && npm pkg set type=module
npm install express @sovaria/sdkSOVARIA_URL=https://<project-ref>.sovarias.in
SOVARIA_ANON_KEY=<anon-key>#2. The server
import express from 'express'
import { createClient, SovariaError } from '@sovaria/sdk'
const { SOVARIA_URL, SOVARIA_ANON_KEY } = process.env
const app = express()
app.use(express.json())
// A fresh client per request. persistSession: false keeps nothing between requests.
const anonymous = () => createClient(SOVARIA_URL, SOVARIA_ANON_KEY, { persistSession: false })
const asUser = (req) => {
const token = req.get('authorization')?.replace(/^Bearer /, '')
if (!token) throw Object.assign(new Error('Sign in first'), { statusCode: 401 })
return anonymous().setAccessToken(token)
}
// data.sql answers { columns, rows, row_count }; rows are arrays in column order.
export const toObjects = ({ columns, rows }) =>
rows.map((row) => Object.fromEntries(columns.map((c, i) => [c, row[i]])))
// Answers { access_token, refresh_token, expires_in, … }; the app keeps the tokens.
app.post('/signup', async (req, res, next) => {
try { res.json(await anonymous().auth.signUp(req.body.email, req.body.password)) } catch (e) { next(e) }
})
app.post('/signin', async (req, res, next) => {
try { res.json(await anonymous().auth.signIn(req.body.email, req.body.password)) } catch (e) { next(e) }
})
app.get('/notes', async (req, res, next) => {
try { res.json(toObjects(await asUser(req).data.sql('select id, body, created_at from notes order by id desc'))) }
catch (e) { next(e) }
})
app.post('/notes', async (req, res, next) => {
try {
const r = await asUser(req).data.sql('insert into notes (body) values ($1) returning id, body', [req.body.body])
res.status(201).json(toObjects(r)[0])
} catch (e) { next(e) }
})
app.use((err, _req, res, _next) => {
const status = err.statusCode || 500
res.status(status).json({ error: err instanceof SovariaError ? err.code : 'ERROR', message: err.message })
})
app.listen(3000, () => console.log('http://localhost:3000'))#3. Run and try it
node --env-file=.env server.js
# in another terminal
TOKEN=$(curl -s localhost:3000/signup -H 'Content-Type: application/json' \
-d '{"email":"ada@example.com","password":"Correct-Horse-9!"}' | jq -r .access_token)
curl -s localhost:3000/notes -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"body":"From Express"}'
curl -s localhost:3000/notes -H "Authorization: Bearer $TOKEN"- Server-to-server calls send no browser
Origin, so allowed origins don't apply here. - Need to act as your app rather than a user (a nightly job, a webhook)? Use the service role key from a secret store, on the server only. It bypasses every rule, so keep those code paths small (API keys).
- To check a token without calling Sovaria, verify it against your project's JWKS (Sessions and tokens).
#Next steps
- Check who can read and change
notesbefore you ship: Tutorial: prove your table is private before you ship. - Push new notes to other devices as they happen: Database change events.
- Every SDK method: JavaScript SDK.
- Run this code next to your data instead: Functions.
#Coming from Firebase or Supabase?
| Platform | What this is called there |
|---|---|
| Firebase | Firebase Admin SDK with Express |
| Supabase | Supabase with Express |
| Sovaria | Quickstart: Node.js and Express |
Moving an existing app? Migrate from Supabase or Firebase imports users (with their passwords), data, files and rules.