본문으로 건너뛰기
오토프로그 홈

12강: 백엔드 기초

v4
작성 2026-04-12읽는 시간 12

데이터 저장하기

이제 데이터를 저장할 차례예요

11강에서 Next.js로 화면을 만들었어요. 레고 블록(컴포넌트)을 조립하고, 색칠 스티커(Tailwind CSS)를 붙여서 멋진 메인 페이지를 완성했죠. 11강 마지막에 이렇게 말씀드렸어요. "데이터베이스가 필요해요. Supabase라는 도구로 데이터 저장을 배워볼 거예요."

바로 오늘이에요.

그런데 왜 데이터베이스가 필요할까요? 지금까지 만든 화면에는 한 가지 문제가 있어요. 뭔가를 입력해도, 새로고침하면 전부 사라져요.

카카오톡을 생각해 보세요. 메시지를 보내고, 앱을 껐다가 다시 켜도 대화 내용이 그대로 있죠? 인스타그램에 사진을 올리면, 다음 날 열어도 그 사진이 남아있어요. 어떻게 가능할까요?

데이터베이스(Database, 데이터를 안전하게 보관하는 창고)가 있기 때문이에요. 지금까지 우리가 만든 건 무대 위 세트장이에요. 보이기만 할 뿐, 아무것도 저장되지 않아요. 오늘은 그 세트장 뒤에 창고를 만들어볼 거예요.


이건 뭐예요?

데이터베이스 = 인터넷 엑셀

엑셀을 써본 적 있으시죠? 엑셀의 구조를 떠올려 보세요.

  • 시트(Sheet) 하나가 하나의 주제예요. "직원 목록" 시트, "매출 현황" 시트처럼요
  • 시트 안에는 (Column)이 있어요. "이름", "이메일", "전화번호" 같은 항목이에요
  • 그리고 (Row)에 실제 데이터가 한 줄씩 들어가요. "홍길동, hong@email.com, 010-1234-5678"

데이터베이스도 완전히 같아요. 이름만 다를 뿐이에요.

엑셀데이터베이스의미
시트테이블(Table)하나의 주제 묶음
컬럼(Column)항목 이름 (이름, 이메일 등)
로우(Row)실제 데이터 한 줄
통합 문서데이터베이스(DB)시트(테이블) 여러 개의 묶음

차이가 있다면, 엑셀은 내 컴퓨터에 파일로 저장되지만, 데이터베이스는 인터넷에 있는 창고에 저장돼요. 그래서 누가 어디서 접속하든 같은 데이터를 볼 수 있어요.

Supabase = 인터넷 엑셀 + 관리자

Supabase(수파베이스)는 이 인터넷 데이터베이스를 아주 쉽게 쓸 수 있도록 만들어 준 서비스예요. BaaS(Backend as a Service, 백엔드를 대신 해주는 서비스)라고 불러요.

비유하자면, 엑셀 프로그램을 설치할 필요 없이 구글 시트를 쓰는 것과 비슷해요. 가입만 하면 바로 테이블을 만들고, 데이터를 넣고, 읽고, 수정하고, 삭제할 수 있어요. 서버(데이터를 보관하고 처리하는 컴퓨터)를 직접 만들 필요가 전혀 없어요.

Supabase를 선택하는 이유는 간단해요.

기준Supabase
배포가 쉬워?가입하면 바로 사용. 서버 설정 불필요
AI가 잘 아나?공식 문서가 풍부하고, Claude가 코드를 아주 잘 만들어줘요
무료로 충분해?무료 플랜으로 프로젝트 2개, 500MB DB 제공
무료 프로젝트는 잠깐 잠들어요

무료 플랜 프로젝트는 일주일 동안 아무 활동이 없으면 자동으로 정지돼요. 없어지는 건 아니고, 대시보드에서 복구 버튼 한 번만 누르면 다시 깨어납니다. 연습하다가 며칠 쉬었다고 당황하지 마세요.


CRUD = 모든 서비스의 기본 뼈대

서비스를 만들 때, 데이터를 다루는 동작은 딱 4가지뿐이에요. 이걸 CRUD(크러드)라고 불러요.

휴대폰 주소록을 생각해 보세요.

동작영어주소록 비유
Create만들기새 연락처 추가
Read읽기연락처 검색해서 보기
Update수정전화번호 변경
Delete삭제연락처 삭제

카카오톡도 CRUD예요. 메시지 보내기(Create), 대화 목록 보기(Read), 메시지 수정(Update), 메시지 삭제(Delete). 인스타그램도요. 사진 올리기(Create), 피드 보기(Read), 캡션 수정(Update), 게시물 삭제(Delete).

세상의 모든 서비스는 이 4가지 동작의 조합이에요. CRUD만 할 줄 알면, 어떤 서비스든 만들 수 있어요.


환경 변수(.env) = 비밀 금고

Supabase에 연결하려면 API 키(API Key, 서비스에 접속하기 위한 비밀 열쇠)가 필요해요. 이 열쇠를 코드에 직접 적으면 안 돼요.

왜냐고요? 코드를 GitHub에 올리면, 전 세계 사람이 내 열쇠를 볼 수 있기 때문이에요. 마치 집 열쇠를 현관문에 테이프로 붙여놓는 것과 같아요. 누구나 가져갈 수 있어요.

그래서 환경 변수(Environment Variable)라는 걸 써요. .env.local이라는 특별한 파일에 비밀 정보를 따로 적어두는 거예요. 이 파일은 GitHub에 절대 올라가지 않아요.

# .env.local 파일 — 비밀 금고 역할
NEXT_PUBLIC_SUPABASE_URL=https://abcdefg.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_A1b2C3d4E5f6G7h8...
  • NEXT_PUBLIC_SUPABASE_URL — Supabase 프로젝트의 주소예요
  • NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY — 프로젝트에 접속하기 위한 공개용 열쇠(publishable key)예요. sb_publishable_ 로 시작해요. "공개용"이라고 해서 아무나 뭐든 할 수 있는 건 아니에요. 허용된 동작만 할 수 있는 제한된 열쇠예요

Supabase 열쇠는 두 종류예요. 헷갈리지 않게 딱 한 번만 정리해 둘게요.

열쇠생김새어디에 쓰나
공개용 열쇠 (publishable)sb_publishable_…브라우저에 나가도 되는 열쇠. 오늘 쓸 것
비밀 열쇠 (secret)sb_secret_…모든 권한을 가진 열쇠. 서버에서만. 오늘은 쓸 일 없어요
예전 강의·블로그에 나오는 anon 키를 찾지 마세요

2025년 11월부터 새로 만든 프로젝트에는 anon 키와 service_role 키가 아예 없습니다. 대신 위의 sb_publishable_ / sb_secret_ 열쇠가 나와요. 인터넷 자료에서 NEXT_PUBLIC_SUPABASE_ANON_KEY 를 보셨다면, 지금은 NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY 로 읽으시면 됩니다. (예전 프로젝트에 남아있는 anon 키도 2026년 말에 없어질 예정이에요)

이 값들은 Supabase 대시보드(프로젝트 관리 화면)에서 복사할 수 있어요. 나중에 실습에서 직접 해볼 거예요.


우리는 이렇게 쓰고 있어요

지금 여러분이 읽고 있는 오토프로그 랩스 사이트는 정적 사이트(데이터베이스 없이 파일만으로 동작하는 사이트)라서, Supabase를 직접 쓰고 있지는 않아요.

하지만 디온웍스에서 운영하는 다른 서비스들은 전부 Supabase를 쓰고 있어요.

  • DGHR(디지HR) — 직원 출퇴근 기록, 휴가 신청, 급여 정보를 Supabase에 저장해요. 직원이 GPS로 출근 체크를 하면 Create, 관리자가 출퇴근 현황을 보면 Read, 퇴근 시간이 수정되면 Update, 잘못된 기록을 삭제하면 Delete. 딱 CRUD예요
  • HoneyERP(허니이알피) — 거래처 정보, 견적서, 발주서 데이터를 전부 Supabase 테이블에 넣어요

이 서비스들 모두, 처음에는 오늘 배울 것과 똑같이 시작했어요. Supabase에 가입하고, 테이블을 만들고, 데이터를 넣고 읽는 것부터요.


한번 써볼까요?

1단계: Supabase 가입 + 프로젝트 만들기

  1. https://supabase.com 에 접속해요
  2. Start your project 버튼을 눌러요
  3. GitHub 계정으로 로그인해요 (5강에서 만든 계정이면 돼요)
  4. New project 를 누르고, 이름을 지어요 (예: my-first-db)
  5. 비밀번호를 설정하고, 지역(Region)은 Northeast Asia (Seoul) 을 선택해요
  6. Create new project 를 누르면 끝이에요. 1~2분이면 프로젝트가 만들어져요

2단계: 테이블 만들기

프로젝트가 만들어지면, 왼쪽 메뉴에서 Table Editor 를 클릭해요. 여기가 우리의 인터넷 엑셀이에요.

New table 버튼을 누르고 이렇게 설정해요.

  • Name: todos (할 일 목록이라는 뜻이에요)
  • Columns (열 = 항목):
    • id — 자동 생성 (기본으로 들어있어요, 건드리지 마세요)
    • title — 타입: text (할 일 제목)
    • is_done — 타입: bool (완료 여부. bool은 참/거짓 두 가지만 있는 타입이에요). Default Value 칸에 false 를 넣어주세요
    • created_at — 자동 생성 (기본으로 들어있어요)

is_done 의 기본값을 비워두면 새 할 일이 "참도 거짓도 아닌 상태"로 들어가서, 체크박스가 눌리지 않는 것처럼 보일 수 있어요. false 한 단어만 적어두면 됩니다.

Save 를 누르면, todos 테이블이 만들어져요. 엑셀에서 시트 하나를 새로 만든 거예요.

만드는 화면에 Enable Row Level Security (RLS) 라는 체크박스가 켜져 있을 거예요. 그대로 두세요. 이게 창고 문에 걸리는 자물쇠인데, 잠시 뒤 4단계에서 열쇠(정책)를 만들어 줄 거예요.

3단계: 접속 정보 확인하기

상단 메뉴의 Connect 버튼을 누르면 연결에 필요한 값이 한 화면에 다 보여요. 이 버튼이 제일 확실한 길이에요. (왼쪽 아래 Project SettingsAPI Keys 에서도 열쇠를 볼 수 있어요)

  • Project URLhttps://xxxxxx.supabase.co 형태의 주소
  • Publishable keysb_publishable_ 로 시작하는 문자열. 브라우저에서 쓸 수 있는 공개용 열쇠예요

sb_secret_ 로 시작하는 열쇠도 같이 보일 텐데, 그건 오늘 쓰지 않아요. 복사하지 마세요.

이 두 값을 복사해서, 프로젝트 폴더에 .env.local 파일을 만들어 넣어요.

4단계: 프로젝트에 Supabase 연결하기

11강에서 만든 Next.js 프로젝트에 Supabase를 연결할 거예요.

# 11강에서 만든 프로젝트 폴더로 이동
cd ~/my-service

# Supabase JavaScript 클라이언트 설치
npm install @supabase/supabase-js@latest

.env.local 파일을 프로젝트 루트(최상위 폴더)에 만들어요.

# .env.local — Supabase 접속 정보 (GitHub에 올라가지 않아요)
NEXT_PUBLIC_SUPABASE_URL=여기에_아까_복사한_Project_URL을_붙여넣으세요
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=여기에_아까_복사한_publishable_키를_붙여넣으세요

5단계: 창고 문 열어주기 (제일 많이 막히는 곳)

여기가 초보자가 가장 많이 막히는 단계예요. 미리 해두고 갑시다.

2단계에서 만든 todos 테이블에는 자물쇠(RLS)가 걸려 있어요. 이 상태에서는 우리 웹페이지가 데이터를 달라고 해도 에러도 안 나고 그냥 빈 목록이 돌아와요. 코드가 멀쩡한데 화면이 텅 비어 있으니, 뭐가 잘못됐는지 찾기가 정말 어렵죠.

그래서 출입증을 만들어 줘야 해요. 이걸 정책(Policy)이라고 불러요.

왼쪽 메뉴에서 SQL Editor 를 누르고, 아래 네 줄을 그대로 붙여넣은 다음 Run 을 눌러요.

-- todos 테이블을 웹페이지에서 쓸 수 있게 열어주는 출입증
grant select, insert, update, delete on public.todos to anon;
alter table public.todos enable row level security;
drop policy if exists "연습용 전체 허용" on public.todos;
create policy "연습용 전체 허용" on public.todos for all to anon using (true) with check (true);

무슨 뜻인지 몰라도 괜찮아요. "이 테이블은 우리 웹페이지가 읽고 쓸 수 있게 허락한다"는 뜻이에요. Success. No rows returned 라고 나오면 성공이에요.

세 번째 줄(drop policy if exists)은 같은 이름의 출입증이 이미 있으면 먼저 치우는 줄이에요. 이게 없으면 이 SQL을 두 번째 실행할 때 policy "연습용 전체 허용" for table "todos" already exists 에러가 나면서 멈춰버려요. 뭔가 꼬여서 다시 실행할 일이 자주 생기니까 넣어뒀어요.

여기 나오는 anon 은 아까 없어졌다고 한 열쇠 이름이 아니라, 데이터베이스 안에서 "로그인 안 한 방문자"를 가리키는 이름이에요. 우리가 쓰는 공개용 열쇠가 바로 이 이름으로 들어가요. 이름이 겹쳐서 헷갈릴 뿐, 다른 것입니다.

이 정책은 연습용이에요

위 정책은 누구나 읽고 쓸 수 있게 열어두는 설정이에요. 혼자 연습하는 할 일 목록에는 괜찮지만, 진짜 서비스에 이대로 쓰면 안 돼요. 로그인 기능을 붙이면 "내 데이터만 보이게" 하는 정책으로 바꿉니다. 그건 나중 강에서 다뤄요.


클로드 코드 터미널에서는 이렇게

전체 흐름을 처음부터 끝까지 정리해 드릴게요.

# ── 1. 프로젝트 폴더로 이동 (일반 터미널에서) ──
cd ~/my-service

# ── 2. Supabase 클라이언트 설치 ──
npm install @supabase/supabase-js@latest

# ── 3. Claude Code 시작 ──
claude

Claude Code가 열리면, 먼저 Supabase 연결 파일을 만들어 달라고 해요.

클로드 코드 대화창에서 !를 맨 앞에 붙이면 터미널 명령어를 바로 실행할 수 있어요. 결과가 나오면 클로드가 한마디 덧붙여 주기도 해요. 단, !는 터미널에서 연 클로드 코드에서만 되고, VS Code 확장 프로그램의 채팅 창에서는 안 돼요.

너는 10년차 풀스택 개발자야.
나는 코딩 1줄도 모르는 바이브 코더야.

Supabase 클라이언트 연결 파일을 만들어줘.

요구사항:
1. lib/supabase.ts 파일을 새로 만들어줘
2. .env.local에서 NEXT_PUBLIC_SUPABASE_URL과 NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY를 읽어서 Supabase 클라이언트를 생성해
3. createClient 함수를 import해서 쓰면 돼

안 할 것:
- 다른 파일은 건드리지 마
- 서버 사이드 렌더링 관련 설정은 하지 마 (지금은 클라이언트만)
- 외부 라이브러리 추가하지 마 (@supabase/supabase-js는 이미 설치됨)

코드에 한글 주석 달아줘.

Claude가 만들어준 lib/supabase.ts 파일(Supabase 연결 설정 파일)은 대략 이런 모습이에요.

// lib/supabase.ts — Supabase 연결 설정 파일
import { createClient } from '@supabase/supabase-js'

// .env.local에서 환경 변수를 읽어와요
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!

// Supabase 클라이언트 생성 — 이 객체로 DB에 접근해요
export const supabase = createClient(supabaseUrl, supabaseKey)

11강에서 설치할 때 src/ 디렉터리를 쓰는 설정을 골랐다면, 위 경로 앞에 src/를 붙이면 돼요(src/lib/supabase.ts). 구조와 원리는 똑같습니다.

이제 CRUD를 실습해볼 거예요. Claude에게 할 일 목록 페이지를 만들어 달라고 해요.

todos 테이블과 연동하는 할 일 목록 페이지를 만들어줘.

Supabase todos 테이블 구조:
- id: 자동 생성 (숫자)
- title: text (할 일 제목)
- is_done: bool (완료 여부, 기본값 false)
- created_at: 자동 생성 (날짜)

기능 (CRUD 전부):
1. Create — 입력 필드에 할 일을 적고 "추가" 버튼을 누르면 DB에 저장
2. Read — 페이지를 열면 저장된 할 일 목록이 전부 보임
3. Update — 할 일을 클릭하면 완료/미완료 토글(체크 표시)
4. Delete — 할 일 옆의 "삭제" 버튼을 누르면 DB에서 삭제

파일:
- app/todos/page.tsx에 만들어줘 (새 페이지)
- lib/supabase.ts는 이미 만들어져 있어 (import해서 써)
- 브라우저에서 동작하는 페이지니까 맨 위에 'use client'를 붙여줘

디자인:
- Tailwind CSS 사용
- 다크 배경, 밝은 글씨, 시안 포인트(#06b6d4)
- 모바일 반응형

안 할 것:
- 다른 파일은 건드리지 마
- 로그인/인증은 아직 하지 마
- 외부 라이브러리 추가하지 마

코드에 한글 주석 달아줘.

페이지가 만들어지면 확인해 보세요.

# ── 4. 개발 서버 실행 ──
! npm run dev

브라우저에서 http://localhost:3000/todos 를 열어보세요. 할 일을 추가하고, 체크하고, 삭제해 보세요. 새로고침해도 데이터가 남아있어요. 이게 데이터베이스의 힘이에요.

목록이 계속 비어 있거나 추가가 안 된다면

십중팔구 5단계(창고 문 열어주기)를 건너뛴 것이에요. Supabase 대시보드의 SQL Editor에서 5단계 네 줄을 실행했는지 확인해 보세요. permission denied for table todos 같은 메시지가 보이면 확실합니다. 그래도 안 되면 .env.local 의 열쇠 이름이 NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY 가 맞는지, 값을 넣고 개발 서버를 껐다 켰는지 확인하세요. (.env.local은 서버를 다시 시작해야 반영돼요)

# ── 5. 잘 되면 커밋 ──
! git add .
! git commit -m "Supabase 연동 + 할 일 목록 CRUD 완성"

CRUD 코드가 실제로 뭘 하는 건지

Claude가 만들어준 코드 안에는 이런 패턴이 반복돼요. 직접 쓸 필요는 없지만, 어떤 일이 벌어지는지 알아두면 Claude에게 더 정확한 지시를 할 수 있어요.

Create (만들기) — 새 데이터를 테이블에 넣어요.

// "할 일 목록" 테이블에 새 항목 추가
const { data, error } = await supabase
.from('todos') // todos 테이블에서
.insert({ title: '장보기' }) // 이 데이터를 넣어줘
.select() // 방금 넣은 걸 돌려줘

.select() 를 안 붙이면 저장은 되지만 data가 비어 있어요. 수정(update)·삭제(delete)도 마찬가지예요. 결과를 화면에 바로 반영하려면 붙여주세요.

Read (읽기) — 테이블에서 데이터를 가져와요.

// "할 일 목록" 테이블의 모든 항목 가져오기
const { data, error } = await supabase
.from('todos') // todos 테이블에서
.select('*') // 전부 다 가져와줘

Update (수정) — 기존 데이터를 바꿔요.

// id가 1인 할 일의 완료 상태를 true로 변경
const { data, error } = await supabase
.from('todos') // todos 테이블에서
.update({ is_done: true }) // 이 값을 바꿔줘
.eq('id', 1) // id가 1인 것만

Delete (삭제) — 데이터를 지워요.

// id가 1인 할 일을 삭제
const { data, error } = await supabase
.from('todos') // todos 테이블에서
.delete() // 삭제해줘
.eq('id', 1) // id가 1인 것만

패턴이 보이시죠? 항상 supabase.from('테이블 이름') 으로 시작해요. 그다음에 .insert(), .select(), .update(), .delete() 중 하나를 붙이면 끝이에요. 엑셀에서 행을 추가하고, 검색하고, 수정하고, 삭제하는 것과 똑같아요.


이번 강에서 기억할 것

핵심만 정리해 볼게요.

  1. 데이터베이스는 인터넷 엑셀이에요. 테이블(시트), 컬럼(열), 로우(행)로 구성돼요. 내 컴퓨터가 아니라 인터넷 창고에 저장되니까, 새로고침해도 데이터가 사라지지 않아요.

  2. Supabase는 데이터베이스를 쉽게 쓰게 해주는 서비스예요. 가입만 하면 테이블을 만들고 데이터를 관리할 수 있어요. 서버를 직접 만들 필요가 없어요.

  3. CRUD는 모든 서비스의 뼈대예요. Create(만들기), Read(읽기), Update(수정), Delete(삭제). 이 4가지 동작의 조합으로 어떤 서비스든 만들 수 있어요.

  4. API 키는 코드에 직접 쓰면 안 돼요. .env.local 파일(비밀 정보 보관용 파일)에 따로 보관하세요. 이 파일은 GitHub에 올라가지 않으니까 안전해요. 오늘 쓴 열쇠는 sb_publishable_ 로 시작하는 공개용 열쇠예요.

  5. 새 테이블은 잠긴 채로 태어나요. RLS(자물쇠)가 기본으로 걸려 있어서, 정책(출입증)을 만들기 전까지는 데이터가 안 보여요. 에러도 안 나고 그냥 빈 목록이 나오니까, 화면이 비어 있으면 제일 먼저 여길 의심하세요.

  6. Supabase 코드 패턴은 하나예요. supabase.from('테이블').동작() — 이 구조만 기억하면 돼요. 실제 코드는 Claude가 만들어줘요.

다음 강에서는 13강: API 연동을 다뤄요. 오늘은 우리가 만든 데이터베이스에 직접 데이터를 넣고 읽었어요. 그런데 날씨 정보, 지도, 결제 같은 건 우리가 직접 만들 수 없잖아요? 다음 강에서는 남이 이미 만들어둔 기능을 가져다 쓰는 방법을 배워요. API(Application Programming Interface)라는 주문 창구를 통해서요.


이런 분께 추천해요

백엔드 기초는 "화면은 만들었는데, 데이터를 저장하고 싶은 분"을 위한 다음 단계예요.

  • 11강에서 Next.js로 화면을 만들었는데, 새로고침하면 다 사라져서 아쉬웠던 분 — Supabase를 연결하면 데이터가 영구 보관돼요.
  • 할 일 목록, 게시판, 회원 관리 같은 기능을 내 서비스에 넣고 싶은 분 — 모든 기능의 핵심은 CRUD예요. 오늘 배운 패턴으로 전부 만들 수 있어요.
  • 서버를 직접 만드는 건 엄두가 안 나는 분 — Supabase가 서버 역할을 대신해줘요. 가입하고 테이블만 만들면 돼요.

참고 링크

개정 이력4
  • v42026-07-191~20강 공통으로 반복되던 `!` 셸 모드 안내 문구를 하나로 통일 — 실행 결과에 클로드가 한마디 덧붙인다는 현행 동작과, 터미널에서 연 클로드 코드에서만 되고 VS Code 확장 채팅 창에서는 안 된다는 조건을 추가
  • v32026-07-195단계 SQL을 두 번 실행해도 안 깨지게 drop policy if exists 한 줄 추가(재실행 시 already exists 에러로 멈추던 문제)와 본문 "세 줄→네 줄" 정정. 2단계에서 is_done의 Default Value를 false로 지정하도록 보강(기본값이 비면 체크가 안 먹는 것처럼 보임)
  • v22026-07-19Supabase 신규 키 체계(publishable) 반영, RLS·GRANT 단계 신설, 대시보드 경로(Connect)·버튼 이름 최신화, 11강 폴더 구조(src 미사용)에 맞춰 경로 정정, insert 후 select 체이닝 보완
  • v12026-04-12초판