PostgREST
포스트그레스트
PostgreSQL을 RESTful API로 자동 노출. 스키마 기반 API.
포스트그레스트
PostgreSQL을 RESTful API로 자동 노출. 스키마 기반 API.
PostgREST는 PostgreSQL 데이터베이스를 RESTful API로 자동 변환하는 독립 실행형 웹 서버입니다. 별도의 백엔드 코드 없이 데이터베이스 테이블, 뷰, 함수를 HTTP 엔드포인트로 노출하여 CRUD 작업을 수행할 수 있습니다. 데이터베이스 스키마가 곧 API 명세가 되는 "스키마 퍼스트" 접근 방식입니다.
PostgREST는 PostgreSQL의 Row Level Security(RLS)를 활용한 인증/인가를 지원합니다. JWT 토큰의 클레임을 PostgreSQL role로 매핑하여, 데이터베이스 수준에서 행 단위 접근 제어가 가능합니다. 이는 애플리케이션 로직이 아닌 데이터베이스에서 보안을 강제하므로 더 안전합니다.
API 요청은 URL 파라미터를 통해 필터링, 정렬, 페이지네이션, 컬럼 선택이 가능합니다. `?select=id,name`, `?order=created_at.desc`, `?limit=10&offset=20` 같은 쿼리 문법을 지원하며, 관계형 데이터를 임베딩하여 한 번의 요청으로 조인된 데이터를 가져올 수도 있습니다.
Supabase는 PostgREST를 핵심 컴포넌트로 사용하여 "Firebase 대안"을 제공합니다. PostgREST에 실시간 구독, 인증, 스토리지 등을 추가한 완전한 BaaS(Backend as a Service)입니다. 프로토타입을 빠르게 만들거나, 간단한 CRUD 앱에서 백엔드 개발 시간을 크게 절약할 수 있습니다.
-- === PostgreSQL 스키마 정의 ===
-- 테이블 생성
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
category VARCHAR(100),
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE reviews (
id SERIAL PRIMARY KEY,
product_id INT REFERENCES products(id),
user_id INT,
rating INT CHECK (rating >= 1 AND rating <= 5),
comment TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Row Level Security 활성화
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
ALTER TABLE reviews ENABLE ROW LEVEL SECURITY;
-- 읽기 정책: 모든 사용자가 읽기 가능
CREATE POLICY "Products are viewable by everyone"
ON products FOR SELECT
USING (true);
-- 쓰기 정책: 인증된 사용자만 리뷰 작성 가능
CREATE POLICY "Users can insert their own reviews"
ON reviews FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- API를 위한 뷰 생성
CREATE VIEW product_with_rating AS
SELECT
p.*,
COALESCE(AVG(r.rating), 0) AS avg_rating,
COUNT(r.id) AS review_count
FROM products p
LEFT JOIN reviews r ON p.id = r.product_id
GROUP BY p.id;
// === JavaScript/TypeScript 클라이언트 사용 예시 ===
// 기본 CRUD 작업
const API_URL = 'https://your-project.supabase.co/rest/v1';
const API_KEY = 'your-anon-key';
const headers = {
'apikey': API_KEY,
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
'Prefer': 'return=representation' // INSERT/UPDATE 후 결과 반환
};
// GET - 전체 상품 조회
const getAllProducts = async () => {
const response = await fetch(`${API_URL}/products`, { headers });
return response.json();
};
// GET - 필터링, 정렬, 페이지네이션
const getFilteredProducts = async () => {
const params = new URLSearchParams({
'select': 'id,name,price,category', // 컬럼 선택
'category': 'eq.electronics', // 필터: category = 'electronics'
'price': 'lte.1000', // 필터: price <= 1000
'order': 'price.asc', // 정렬: 가격 오름차순
'limit': '10', // 페이지 크기
'offset': '0' // 시작 위치
});
const response = await fetch(`${API_URL}/products?${params}`, { headers });
return response.json();
};
// GET - 관계 데이터 임베딩 (JOIN)
const getProductsWithReviews = async () => {
// 상품과 함께 리뷰도 가져오기
const response = await fetch(
`${API_URL}/products?select=*,reviews(*)`,
{ headers }
);
return response.json();
};
// GET - 뷰 조회 (평균 평점 포함)
const getProductsWithRating = async () => {
const response = await fetch(
`${API_URL}/product_with_rating?order=avg_rating.desc`,
{ headers }
);
return response.json();
};
// POST - 상품 추가
const createProduct = async (product: {
name: string;
price: number;
category: string;
}) => {
const response = await fetch(`${API_URL}/products`, {
method: 'POST',
headers,
body: JSON.stringify(product)
});
return response.json();
};
// PATCH - 상품 수정
const updateProduct = async (id: number, updates: Partial<Product>) => {
const response = await fetch(`${API_URL}/products?id=eq.${id}`, {
method: 'PATCH',
headers,
body: JSON.stringify(updates)
});
return response.json();
};
// DELETE - 상품 삭제
const deleteProduct = async (id: number) => {
await fetch(`${API_URL}/products?id=eq.${id}`, {
method: 'DELETE',
headers
});
};
// === Supabase JavaScript 클라이언트 (더 간편한 방식) ===
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key'
);
// 동일한 작업을 더 간편하게
const { data, error } = await supabase
.from('products')
.select('*, reviews(*)')
.eq('category', 'electronics')
.lte('price', 1000)
.order('price', { ascending: true })
.range(0, 9);
// 실시간 구독
const subscription = supabase
.channel('products-changes')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'products' },
(payload) => {
console.log('변경 감지:', payload);
}
)
.subscribe();
"백엔드 개발자 없이 빠르게 프로토타입을 만들어야 하는데, 어떻게 하면 좋을까요?"
"Supabase나 PostgREST를 쓰면 됩니다. PostgreSQL에 테이블만 정의하면 자동으로 REST API가 생성돼요. CRUD는 물론 필터링, 페이지네이션, JOIN까지 URL 파라미터로 처리할 수 있어서 별도 백엔드 코드 없이 프론트엔드만으로 기능 구현이 가능해요."
"PostgREST로 API를 만들면 보안은 어떻게 처리해요?"
"PostgreSQL의 Row Level Security(RLS)를 사용해요. 데이터베이스 수준에서 '이 행은 이 사용자만 접근 가능'처럼 정책을 정의하면, PostgREST가 JWT 토큰을 파싱해서 해당 role로 쿼리를 실행해요. 애플리케이션이 아닌 DB에서 보안을 강제하니까 더 안전합니다."
"PostgREST의 한계점은 무엇인가요?"
"복잡한 비즈니스 로직이나 외부 API 연동이 필요한 경우 한계가 있어요. 단순 CRUD를 넘어서는 작업은 PostgreSQL 함수로 구현하거나 별도 서비스가 필요합니다. 또한 스키마 변경이 곧 API 변경이므로, 버전 관리와 하위 호환성 유지에 주의해야 해요."
RLS 필수: PostgREST를 프로덕션에서 사용할 때는 반드시 Row Level Security를 활성화하세요. RLS 없이 공개하면 누구나 모든 데이터에 접근할 수 있습니다. 테이블별로 적절한 SELECT, INSERT, UPDATE, DELETE 정책을 정의해야 합니다.
스키마 노출: PostgREST는 데이터베이스 스키마를 그대로 API로 노출합니다. 민감한 컬럼이나 테이블은 별도 스키마로 분리하거나, 뷰를 사용해 필요한 컬럼만 노출하세요. 기본적으로 public 스키마만 노출됩니다.
복잡한 로직: 트랜잭션이 필요한 복잡한 작업, 외부 서비스 연동, 비동기 작업 등은 PostgreSQL 함수나 별도 백엔드가 필요합니다. PostgREST는 CRUD 중심의 간단한 API에 최적화되어 있습니다.