Can I query Postgres from a frontend without a backend?

Yes. supabase-js calls the Data API straight from the browser with the anon key, and Row Level Security decides which rows each user can read or change.

Yes. That's the main way Supabase apps work. Your frontend uses supabase-js with the project's anon key to call the Data API, and Row Level Security (RLS) policies in Postgres decide what each request may see or change. You don't need your own API server, as long as every table in public has RLS turned on and sensible policies.

How it stays safe

The anon key is meant to be public. It only identifies your project; on its own it gets the permissions of the anon Postgres role. When a user signs in, supabase-js sends their access token instead, and Postgres runs the query as authenticated with auth.uid() set to that user's ID. Policies use that ID to filter rows.

The service_role key is different. It bypasses RLS entirely, so it must never appear in browser or mobile code.

Example: a team chat

create table public.messages (
  id bigint generated always as identity primary key,
  room_id uuid not null,
  author_id uuid not null default auth.uid() references auth.users (id),
  body text not null,
  created_at timestamptz not null default now()
);
alter table public.messages enable row level security;

create policy "members read room messages" on public.messages
  for select to authenticated
  using (room_id in (select room_id from public.room_members where user_id = (select auth.uid())));

create policy "members post as themselves" on public.messages
  for insert to authenticated
  with check (
    (select auth.uid()) = author_id
    and room_id in (select room_id from public.room_members where user_id = (select auth.uid()))
  );

In the browser:

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'https://app-one.acme.supahost.space',
  import.meta.env.VITE_SUPABASE_ANON_KEY
)

const { data, error } = await supabase
  .from('messages')
  .select('id, body, created_at')
  .eq('room_id', roomId)
  .order('created_at', { ascending: false })
  .limit(50)

A user who isn't in the room gets an empty array, not an error, because RLS filters rows out silently.

Things to know on SupaHost

  • The Data API exposes public, storage, graphql_public and pgmq_public. That list is fixed, so keep private tables in another schema or behind RLS.
  • A request returns at most 1000 rows; page through bigger results with .range().
  • When something needs a secret (a payment API key, for example), put that part in an Edge Function and call it with supabase.functions.invoke().

FAQ

Is it safe to put the anon key in my frontend code?

Yes, provided RLS is enabled on every exposed table. The anon key gives no more access than your policies allow.

What happens if I forget to enable RLS?

The table can be read and changed by anyone with your anon key, which is public. Turn it on with alter table ... enable row level security and add policies. The MCP get_advisors tool flags tables without RLS.

When do I still need a backend?

When you use secrets, call third-party APIs, process payments or run long jobs. On SupaHost, Edge Functions cover most of these.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us