# Querying With supabase-js — Supabase

Source: https://www.skillbyai.com/en/supabase/d-query

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

```typescript
// 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.

**Quiz:** How do you return the inserted row from an insert?

- [ ] Call .returnRow()
- [x] 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.
