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.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