클라이언트에서 복잡한 SQL 쿼리를 직접 조합하다 보면 코드가 금세 난잡해진다. Supabase RPC를 사용하면 PostgreSQL 함수를 서버에 정의해두고, 클라이언트에서는 함수 이름만으로 호출할 수 있다. 비즈니스 로직을 DB 레이어에 캡슐화하면서도 REST API처럼 깔끔하게 사용할 수 있는 방법이다.
RPC(Remote Procedure Call)는 원격 서버에 정의된 프로시저를 로컬 함수처럼 호출하는 패턴이다. Supabase에서는 PostgreSQL Function을 PostgREST가 자동으로 HTTP 엔드포인트로 노출하며, supabase.rpc는 그 엔드포인트를 추상화한 클라이언트 메서드다.
PostgREST — PostgreSQL 스키마를 자동으로 REST API로 변환해주는 오픈소스 서버. Supabase 내부에서 동작한다.
클라이언트
│
│ supabase.rpc('fn_name', { arg: value })
▼
PostgREST ──▶ POST /rest/v1/rpc/fn_name
│
▼
PostgreSQL Function
│
▼
Result (JSON)
SQL 에디터 또는 마이그레이션 파일에서 함수를 정의한다.
-- 특정 유저의 총 주문 금액을 반환하는 함수
CREATE OR REPLACE FUNCTION get_user_total_amount(user_id UUID)
RETURNS NUMERIC
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
total NUMERIC;
BEGIN
SELECT COALESCE(SUM(amount), 0)
INTO total
FROM orders
WHERE orders.user_id = get_user_total_amount.user_id;
RETURN total;
END;
$$;
SECURITY DEFINER — 함수를 호출한 유저가 아닌, 함수를 정의한 유저(보통 postgres)의 권한으로 실행된다. RLS를 우회할 수 있으므로 신중하게 사용해야 한다.
LANGUAGE plpgsql 대신 단순 쿼리라면 LANGUAGE sql로도 작성할 수 있다.
CREATE OR REPLACE FUNCTION get_user_total_amount(user_id UUID)
RETURNS NUMERIC
LANGUAGE sql
SECURITY DEFINER
AS $$
SELECT COALESCE(SUM(amount), 0)
FROM orders
WHERE orders.user_id = user_id;
$$;
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
const { data, error } = await supabase.rpc('get_user_total_amount', {
user_id: 'some-uuid',
})
if (error) {
console.error(error)
} else {
console.log('총 주문 금액:', data) // number
}
함수 인자 이름은 PostgreSQL 함수의 파라미터 이름과 일치해야 한다.
스칼라 값 대신 여러 행을 반환하려면 RETURNS TABLE을 사용한다.
CREATE OR REPLACE FUNCTION get_recent_orders(user_id UUID, limit_count INT DEFAULT 10)
RETURNS TABLE(order_id UUID, amount NUMERIC, created_at TIMESTAMPTZ)
LANGUAGE sql
AS $$
SELECT id, amount, created_at
FROM orders
WHERE orders.user_id = get_recent_orders.user_id
ORDER BY created_at DESC
LIMIT limit_count;
$$;
const { data, error } = await supabase.rpc('get_recent_orders', {
user_id: 'some-uuid',
limit_count: 5,
})
// data: Array<{ order_id: string, amount: number, created_at: string }>
| 구분 | 실행 권한 | RLS 적용 | 사용 시점 |
|---|---|---|---|
| SECURITY DEFINER | 함수 소유자 | 우회됨 | 집계, 통계, 관리자 로직 |
| SECURITY INVOKER | 호출한 유저 | 적용됨 | 유저 데이터 조회, 일반 쿼리 |
기본값은 SECURITY INVOKER다. SECURITY DEFINER 함수는 반드시 내부에서 입력값을 검증하고 최소 권한 원칙을 지켜야 한다.
복잡한 JOIN, 집계, 또는 여러 테이블을 조작하는 트랜잭션 로직은 RPC로 DB에 캡슐화하는 것이 낫다. 반면 단순한 CRUD는 Supabase 클라이언트의 .from.select 체인으로 충분하다. RPC는 클라이언트 코드를 단순하게 유지하면서 DB의 연산 능력을 최대로 활용하는 수단이다.