Aurabase Logo
aurabasedocs
docs›Guides›Query your database (SDK)

Query your database with the JavaScript SDK

The everyday flows of `aura.db`, the rules that refuse unsafe calls, and how to protect a table with row-level security.

9 min read·Level intermediate·Revised on Oct 12, 2026
This English text was generated automatically from the French original and has not been reviewed yet.
#
Read

1. Read rows

read.tstypescript
const { data, error } = await aura
  .from('todos')
  .select('id, title, done')
  .eq('done', false)
  .order('created_at', { ascending: false })
  .limit(20)

aura.from('todos') is a shortcut for aura.db.from('todos'). Every call returns { data, error } and never throws, see client options and errors.

Filters chain: eq, neq, gt, gte, lt, lte, like, ilike, in, is, and the others listed in the database reference. For a page of rows, use range(from, to).

#
Write

2. Insert, update and delete

write.tstypescript
// Insert one row, or an array of rows, and get them back
const { data } = await aura.from('todos').insert({ title: 'Write the docs' }).select()

// Upsert
await aura.from('todos').upsert({ id, title: 'Renamed' }).select()

// Update and delete need a filter
await aura.from('todos').update({ done: true }).eq('id', id)
await aura.from('todos').delete().eq('id', id)

Two rules protect you from mistakes:

  • A batch insert needs the same keys in every row. Rows with different keys return 400 with All object keys must match. Fill the missing keys with null.
  • update() and delete() without a filter are refused by the client with the error code unfiltered_mutation, before any request is sent. To change every row on purpose, run SQL from a server.
#
Results

3. One row, many rows, counts

count.tstypescript
// Exactly one row: an error if there are none or several
const one = await aura.from('todos').select('*').eq('id', id).single()
// → error.status 406 when the filter matches zero or several rows

// Zero or one row: data is null when nothing matches
const maybe = await aura.from('todos').select('*').eq('id', id).maybeSingle()

// Rows and their total
const page = await aura.from('todos').select('*', { count: 'exact' }).range(0, 9)
// page.count is the total, whatever the range

// The total only, no rows
const { count } = await aura.from('todos').select('*', { count: 'exact', head: true })

count: 'exact' counts every matching row. 'planned' and 'estimated' are faster on very large tables and approximate.

#
Security

4. Protect a table

A table created in SQL is open until you enable RLS

Row-level security is off on a new table. With the public key, anyone can read and write it. Enable it, then add the policies you need:

SQLsql
alter table todos enable row level security;

create policy todos_own_read on todos
  for select to aura_authenticated
  using (auth.uid() = owner);

The database roles are aura_anon, aura_authenticated and aura_service_role. auth.uid() is NULL for the public key and a user id for a signed-in user. See Row-Level Security.

A service_role key bypasses RLS by design: keep it on your server.

#
Functions

5. SQL functions and rpc

Create a function from a server, then call it from anywhere with rpc():

server/setup.tstypescript
await admin.db.createFunction({
  name: 'add',
  args: [
    { name: 'a', data_type: 'int', mode: 'IN' },
    { name: 'b', data_type: 'int', mode: 'IN' },
  ],
  returns: 'int',
  language: 'sql',
  body: 'SELECT a + b',
  grant_execute_to: ['anon', 'authenticated'],
})
client.tstypescript
const { data } = await aura.rpc('add', { a: 1, b: 2 }) // → 3
  • grant_execute_to is required to call it. Without it, nobody can execute the function. A role that is not listed gets 403 permission denied for function. Both authenticated and aura_authenticated are accepted.
  • A new function is callable after a few seconds. Until the API reloads its schema, rpc() returns 404 Could not find the function.
  • Creating functions with raw SQL (CREATE FUNCTION) is refused: use createFunction.
#
Server

6. Run SQL from a server

server/migrate.tstypescript
import { createAdminClient } from '@aurabase/aurabase-js'

const admin = createAdminClient(process.env.AURA_URL!, process.env.AURA_SERVICE_ROLE_KEY!)

await admin.db.sql(`
  create table if not exists todos (
    id uuid primary key default gen_random_uuid(),
    title text not null,
    done boolean not null default false,
    owner uuid,
    created_at timestamptz not null default now()
  )
`)

db.sql() needs a service_role key. It refuses a DELETE without a WHERE, and it does not read system catalogs such as pg_class or pg_roles. To list your tables, use admin.db.schema().

Last updated · Oct 12, 2026