개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Supabase RPC — PostgreSQL 함수를 API처럼 호출하는 방법

2025년 4월 18일
SupabaseRPCDatabase FunctionPostgreSQL Functionsupabase.rpc

클라이언트에서 복잡한 SQL 쿼리를 직접 조합하다 보면 코드가 금세 난잡해진다. Supabase RPC를 사용하면 PostgreSQL 함수를 서버에 정의해두고, 클라이언트에서는 함수 이름만으로 호출할 수 있다. 비즈니스 로직을 DB 레이어에 캡슐화하면서도 REST API처럼 깔끔하게 사용할 수 있는 방법이다.

RPC란

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)

PostgreSQL Function 작성

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;
$$;

supabase.rpc 호출

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 }>

SECURITY DEFINER vs SECURITY INVOKER

구분실행 권한RLS 적용사용 시점
SECURITY DEFINER함수 소유자우회됨집계, 통계, 관리자 로직
SECURITY INVOKER호출한 유저적용됨유저 데이터 조회, 일반 쿼리

기본값은 SECURITY INVOKER다. SECURITY DEFINER 함수는 반드시 내부에서 입력값을 검증하고 최소 권한 원칙을 지켜야 한다.

언제 RPC를 써야 하는가

복잡한 JOIN, 집계, 또는 여러 테이블을 조작하는 트랜잭션 로직은 RPC로 DB에 캡슐화하는 것이 낫다. 반면 단순한 CRUD는 Supabase 클라이언트의 .from.select 체인으로 충분하다. RPC는 클라이언트 코드를 단순하게 유지하면서 DB의 연산 능력을 최대로 활용하는 수단이다.

← 이전 글Supabase Realtime — 데이터베이스 변경을 실시간으로 구독
다음 글 →Supabase Edge Functions — 서버리스 함수로 백엔드 로직 실행