Lesson 5 / 25
Querying With supabase-js
Select, filter, join and write.
A chainable query builder
from('table') starts a query. select() takes a column list and can embed related tables through foreign keys, e.g. select('id, name, tasks(id, title)'). Filters chain: eq, neq, gt, lt, in, ilike, is, plus order, limit and range for paging. single() expects exactly one row. Writes use insert, update, upsert and delete; add .select() after a write to get the changed rows back. Always filter update and delete, and remember RLS still decides which rows are visible.
Reads and writes
Typical calls (supabase-js v2).
// nested select through the projects -> tasks foreign key
const { data: projects } = await supabase
.from('projects')
.select('id, name, tasks(id, title, done)')
.order('created_at', { ascending: false })
.limit(20)
// insert and return the new row
const { data: task, error } = await supabase
.from('tasks')
.insert({ project_id: 1, title: 'Write docs' })
.select()
.single()
// update with a filter
await supabase.from('tasks').update({ done: true }).eq('id', 42)
// upsert on a unique key, then delete
await supabase.from('tasks').upsert({ id: 42, project_id: 1, title: 'Docs v2' })
await supabase.from('tasks').delete().eq('id', 42)Empty data can mean RLS, not a bug
If a query returns an empty array with no error, a policy may be hiding the rows. Check policies before suspecting the query.
Quick check: How do you return the inserted row from an insert?
- Call .returnRow()
- Chain .select() after .insert()
- Inserts always return rows by default in v2
- Run a second query with the service key
Answer
Chain .select() after .insert() — In v2 writes return no rows unless you select.