백엔드가 무서웠는데, Supabase로 하루 만에 뭔가 만들었다
Article
고백하자면 나는 백엔드가 무서웠다. 퍼블리셔에서 프론트로 넘어올 때도 한참 걸렸는데, 서버는 아예 다른 세계 같았다. DB 스키마, 인증, API 설계 — 단어만 들어도 "그건 백엔드 분들이…" 하고 물러섰다.
그런데 요즘 AI 에이전트랑 일하면서 혼자 커버하는 범위가 계속 넓어지다 보니, 백엔드만 계속 비워두는 게 오히려 어색해졌다. 그래서 주말에 Supabase로 간단한 방명록을 만들어봤다. 결론부터 쓰면, 하루 만에 됐다.
테이블 만들고 나니 API가 이미 있었다
Supabase 대시보드에서 테이블을 하나 만들었다.
create table guestbook (
id uuid primary key default gen_random_uuid(),
nickname text not null,
message text not null,
created_at timestamptz default now()
);
여기서 첫 번째 충격. 테이블을 만들자마자 REST API가 자동으로 생긴다. 별도의 서버 코드 없이 클라이언트에서 바로 쓴다.
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
// 읽기
const { data } = await supabase
.from("guestbook")
.select("*")
.order("created_at", { ascending: false });
// 쓰기
await supabase.from("guestbook").insert({ nickname, message });
Express 강의를 세 번쯤 끊었던 나로서는 허무할 정도였다. "백엔드를 만든다"가 아니라 "데이터 구조를 정의한다"에 가까웠다.
RLS에서 한참 헤맸다
물론 순탄하지만은 않았다. 처음에 insert가 계속 실패했다. 에러 메시지는 new row violates row-level security policy. 콘솔에는 401도 아니고 403도 아니고, 뭐가 문제인지 한참 몰랐다.
원인은 RLS(Row Level Security). Supabase는 테이블마다 행 단위 접근 제어를 걸어두고, 정책(policy)을 명시하지 않으면 기본적으로 아무것도 허용하지 않는다. anon key가 클라이언트에 노출되는 구조라서, "누가 어떤 행을 읽고 쓸 수 있는가"를 DB 레벨에서 선언해야 한다.
alter table guestbook enable row level security;
-- 누구나 읽기 가능
create policy "public read"
on guestbook for select using (true);
-- 누구나 쓰기 가능 (방명록이니까)
create policy "public insert"
on guestbook for insert with check (true);
처음엔 "왜 이렇게까지 막아놨나" 싶었는데, 이해하고 나니 오히려 이 방식이 맞다는 생각이 들었다. 프론트에서 아무리 검증해도 클라이언트는 신뢰할 수 없다. 보안의 최종 방어선은 데이터에 최대한 가까운 곳에 있어야 한다. 프론트만 할 때는 한 번도 해본 적 없는 종류의 생각이었다.
백엔드 감각이 생기니 프론트가 달라진다
만들고 나서 느낀 게 제일 컸다. 데이터가 어디서 태어나서 어떤 규칙으로 흘러오는지를 한 번 직접 설계해보니, 프론트 코드를 보는 눈이 달라졌다.
- 컴포넌트 props를 설계할 때 "이 데이터의 원본 형태가 뭐지"를 먼저 생각하게 된다
- 로딩/에러 상태를 장식이 아니라 당연히 존재하는 상태로 취급하게 된다
- API 응답을 받아서 프론트에서 열심히 가공하던 코드가, 사실 쿼리 한 줄로 끝날 일이었음을 알게 된다
풀스택이 됐다는 얘기가 아니다. 방명록 하나 만든 걸로 그런 말을 하면 안 된다. 다만 반대편 사정을 아는 프론트엔드와 모르는 프론트엔드는 설계가 다르다는 것. 그 차이를 하루짜리 실험으로 얻었으면 충분히 남는 장사다.
다음은 Auth를 붙여볼 생각이다. 소셜 로그인까지 되면 이 블로그에 댓글 기능을 달아볼지도 모르겠다.