Querying with the Client SDK
The idea in one sentence
Section titled “The idea in one sentence”supabase-js’s query builder is a fluent TypeScript wrapper that builds the exact same PostgREST request you could write by hand, and every call resolves to a { data, error } object instead of throwing.
The query builder mirrors the REST API
Section titled “The query builder mirrors the REST API”Everything you saw as a raw query string in the previous lesson has a matching method on the query builder. Pick a table with .from(), choose columns with .select(), filter with .eq() (and its siblings), and sort with .order():
const { data, error } = await supabase .from('movies') .select('id, title, year') .eq('year', 2020) .order('year', { ascending: false });Writes work the same way — .insert(), .update(), and .delete() map to the corresponding HTTP verbs PostgREST exposes:
await supabase.from('movies').insert({ title: 'Arrival', year: 2016, director_id: 3 });
await supabase.from('movies').update({ year: 2021 }).eq('id', 1);
await supabase.from('movies').delete().eq('id', 1);Checking data and error explicitly
Section titled “Checking data and error explicitly”A supabase-js query never throws on a failed request — it always resolves with an object shaped { data, error }. If the request succeeds, error is null and data holds the rows. If it fails, data is null and error is populated. Because there is no exception to catch, you must check error yourself on every call, or a failed request will silently look like an empty result.
error is a PostgrestError with four fields worth knowing:
- message — a human-readable description of what went wrong.
- code — either a Postgres error code (e.g. a unique-constraint violation) or a PostgREST-specific code.
- details — additional context from Postgres, when available.
- hint — a suggestion for fixing the problem, when Postgres or PostgREST can offer one.
const { data, error } = await supabase.from('movies').select('id, title').eq('year', 2020);
if (error) { console.error(`query failed [${error.code}]: ${error.message}`);} else { console.log(data);}Embedding related tables through the builder
Section titled “Embedding related tables through the builder”Just like the raw REST API, the query builder can embed a related table by naming it inside select() — PostgREST still does the work by following the foreign key, the builder just gives you a typed, chainable way to ask for it:
const { data, error } = await supabase.from('movies').select('title, directors(name)');A complete example
Section titled “A complete example”import { createClient } from '@supabase/supabase-js';
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!);
async function recentMovies() { const { data, error } = await supabase .from('movies') .select('id, title, year, directors(name)') .eq('year', 2020) .order('title', { ascending: true });
if (error) { console.error('failed to load movies:', error.message); return []; }
return data;}flowchart LR
from[".from('movies')"] --> select[".select('id, title, year')"]
select --> eq[".eq('year', 2020)"]
eq --> order[".order('year')"]
order --> http["One HTTP request to PostgREST"]
http --> result["Result: data and error"]