기술 포스트

백엔드가 무서웠는데, 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를 붙여볼 생각이다. 소셜 로그인까지 되면 이 블로그에 댓글 기능을 달아볼지도 모르겠다.

백엔드가 무서웠는데, Supabase로 하루 만에 뭔가 만들었다 — 디코드랩(DCODELAB)