Field Log · Entry
조카에게 칭찬스티커 만들어주기 2: Supabase로 가족 DB 연결하기
기록 시점: 본문은 2026-08-04에 오프라인 체험판에서 온라인 가족 앱으로 넘어가며 정리한 단계입니다. 2026-09-08에 확인한 프로젝트 README에는 Supabase·FCM을 연결한 가족 파일럿
1.0.0 (4)배포가 기록되어 있습니다. 아래의 미완료 항목은 당시 상태이며, 설치 절차와 실제 가족 기기에서 관찰한 결과를 같은 것으로 보지 않습니다.
오늘의 결론
- Supabase 프로젝트는 단순한 DB 주소가 아니라 Postgres·Auth·Realtime·Storage·Edge Functions를 묶은 이 앱의 온라인 백엔드다.
- 로컬에 이미 있는 migration을
supabase link와db push로 새 원격 프로젝트에 적용한다.- 앱에는 Project URL과 publishable key만 넣는다. secret key나
service_rolekey는 절대 넣지 않는다.- 아이의 익명 로그인도
authenticated역할을 사용하므로 RLS에서is_anonymous를 함께 검사해야 한다.- DB 동기화가 성공해도 원격 푸시는 아직 아니다. Webhook 이후의 EAS·FCM 연결은 3편에서 다룬다.
1편에서 현재 APK가 한 기기 체험판인 이유를 정리했다. 이제 로컬에서 검증한 백엔드를 실제 Supabase 프로젝트에 연결한다.
1. Supabase 프로젝트가 맡는 다섯 가지 일
칭찬별에서 Supabase는 다음 역할을 함께 맡는다.
| Supabase 기능 | 이 앱에서의 역할 |
|---|---|
| Auth | 보호자 이메일 로그인, 아이 익명 기기 계정 |
| Postgres | 가족·할 일·완료 요청·칭찬 요청·스티커 원장 |
| Row Level Security | 자기 가족 데이터만 접근하도록 강제 |
| Realtime | 앱을 열어 둔 가족 기기에 변경 사항 동기화 |
| Private Storage | 아이가 고른 스티커와 배경 이미지 저장 |
| Edge Functions·Webhook | 새 알림 행을 Expo Push API 요청으로 변환 |
프로젝트를 만든다는 것은 이 리소스가 들어갈 원격 공간을 하나 만드는 일이다. project ref는 그 공간을 식별하는 짧은 ID이고, Dashboard URL에서도 확인할 수 있다.
2. 시작 전 준비
이 글은 다음 상태를 기준으로 한다.
praise-sticker/supabase/migrations/202608040001_initial.sql이 존재한다.supabase/functions/push/index.ts가 존재한다.- 로컬
supabase db reset, DB lint, Auth·RLS·Storage 통합 테스트가 통과했다. - 원격 Supabase 프로젝트는 아직 비어 있다.
기존 데이터가 있는 원격 프로젝트에 그대로 따라 하면 안 된다. 먼저 db pull로 원격 변경을 migration에 반영하고 충돌을 검토해야 한다. 아래 순서는 새 프로젝트를 전제로 한다.
코드의 범위: 여기의 migration·RPC·push 함수는 이 가족 앱 전용 코드이며 Supabase가 기본 제공하는 schema가 아닙니다. 공개 저장소의 고정 revision을 제공하지 않으므로 이 글만 복사해 같은 앱을 배포할 수 있다고 가정하지 않습니다. 다른 앱에 적용하려면 가족/구성원 ID, RLS, 알림 테이블, Webhook secret 검증을 먼저 자신의 schema로 구현해야 합니다. 최소 완료 조건은 서로 다른 두 가족 계정에서 조회·승인·비공개 이미지 접근이 교차 허용되지 않는 것입니다.
3. 원격 프로젝트 만들기
- Supabase Dashboard에서 새 프로젝트를 만든다.
- 프로젝트 이름과 강한 DB 비밀번호를 정한다.
- 가족이 주로 있는 지역과 가까운 리전을 선택한다.
- 생성이 끝나면 Dashboard URL의 project ref를 기록한다.
DB 비밀번호, access token, secret key는 메모장이나 Git 파일에 두지 않는다. 비밀번호 관리자를 사용하고 .env는 Git에서 제외한다.
4. CLI로 로컬 프로젝트 연결하기
프로젝트 루트에서 실행한다.
cd praise-sticker
npx supabase login
npx supabase link --project-ref YOUR_PROJECT_REF
login은 Supabase CLI가 계정에 접근하도록 인증하고, link는 현재 로컬 디렉터리가 어느 원격 프로젝트에 db push할지 지정한다. 공식 문서도 원격 배포 전에 이 순서를 사용한다.
연결 대상을 잘못 선택하면 전혀 다른 DB에 migration을 적용할 수 있다. 적용 전에 프로젝트 목록과 migration을 확인한다.
npx supabase projects list
npx supabase migration list
npx supabase db push --dry-run
--dry-run 결과에 예상한 202608040001_initial.sql만 보이는지 확인한 뒤 적용한다.
npx supabase db push
원격 운영 DB에 db reset --linked를 실행해서는 안 된다. 이 명령은 원격 스키마와 데이터를 지우고 migration을 다시 적용하는 파괴적 명령이다.
5. Auth 설정: 보호자와 아이를 다르게 보기
Dashboard의 Authentication 설정에서 다음을 확인한다.
보호자
- Email 로그인을 켠다.
- 최소 비밀번호 길이와 이메일 확인 정책을 정한다.
- 가족 내부 파일럿에서는 가입할 사람과 순서를 미리 정한다.
아이
- Anonymous Sign-ins를 켠다.
- 아이는 이메일·전화번호를 입력하지 않고 일회용 기기 연결 코드로 자기 가족에 연결한다.
여기서 anonymous user와 Supabase의 공개 anon key는 다른 개념이다. signInAnonymously()로 생성된 아이도 API에서는 authenticated Postgres 역할을 사용한다. 따라서 “authenticated이면 보호자”라고 판단하면 안 된다.
칭찬별의 보호자 전용 RPC는 JWT의 is_anonymous와 가족 역할을 함께 검사한다. Supabase도 익명 로그인 활성화 전에 기존 RLS가 익명 사용자를 어떻게 다루는지 검토하라고 안내한다.
6. 앱에 넣을 값과 넣으면 안 되는 값
Dashboard의 Connect 또는 API 설정에서 Project URL과 publishable key를 확인한다. 로컬 개발용 .env에는 다음 두 값만 넣는다.
EXPO_PUBLIC_SUPABASE_URL=https://YOUR_PROJECT_REF.supabase.co
EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
| 값 | 앱에 포함 | 이유 |
|---|---|---|
| Project URL | 가능 | 공개 API 진입점 |
| publishable key | 가능 | RLS를 전제로 한 클라이언트 식별 키 |
service_role 또는 secret key | 금지 | RLS를 우회하는 서버 전용 권한 |
| DB 비밀번호 | 금지 | 관리·migration 연결용 비밀값 |
| Webhook 비밀값 | 금지 | Edge Function과 DB Webhook 사이에서만 사용 |
EXPO_PUBLIC_ 접두사가 붙은 값은 최종 앱 번들에서 사용자가 읽을 수 있다. 이름에 PUBLIC이 붙은 순간 비밀 저장소가 아니다.
7. 비공개 이미지와 RLS 확인하기
migration은 praise-assets 버킷을 비공개로 만들고 storage.objects 정책을 설정한다. 이미지 경로는 다음처럼 가족과 업로더를 포함한다.
<household-id>/<member-id>/<asset-id>.jpg
앱은 24시간짜리 signed URL을 만들어 화면에 표시한다. “버킷이 private”이라는 설정만으로 충분하지 않다. 업로드·조회·삭제마다 storage.objects RLS가 있어야 한다. Supabase Storage는 기본적으로 RLS 정책이 없는 업로드를 허용하지 않는다.
확인할 항목은 다음과 같다.
- 아이가 자기 가족 경로에 이미지를 올릴 수 있는가
- 다른 가족 계정에서는 그 행과 파일이 보이지 않는가
- 아이가 다른 가족 asset ID를 테마로 지정하면 거부되는가
- 보호자가 삭제할 범위와 아이가 삭제할 범위가 의도와 맞는가
8. 푸시 Edge Function 배포하기
DB 안에 app_notifications 행이 생기면 Webhook이 Edge Function을 호출한다. 먼저 Webhook만 알고 있는 긴 임의 비밀값을 만든다.
npx supabase secrets set PUSH_WEBHOOK_SECRET=YOUR_LONG_RANDOM_SECRET
npx supabase functions deploy push
비밀값은 .env에 저장해 배포할 수도 있지만 Git에는 포함하지 않는다. Supabase Edge Function의 secret key 계열 값은 서버에서만 사용하며 앱 번들에 넣지 않는다.
이 프로젝트의 supabase/config.toml은 push 함수의 플랫폼 JWT 검사를 끈다. Database Webhook은 사용자 JWT 대신 x-praise-webhook-secret 헤더로 인증하기 때문이다. 함수 자체는 이 헤더가 없거나 다르면 DB를 읽기 전에 401을 반환한다.
9. Database Webhook 만들기
Supabase Dashboard의 Database Webhooks 화면에서 다음 Webhook을 만든다.
| 설정 | 값 |
|---|---|
| Table | public.app_notifications |
| Event | INSERT |
| Method | POST |
| URL | https://YOUR_PROJECT_REF.supabase.co/functions/v1/push |
| Header | x-praise-webhook-secret: YOUR_LONG_RANDOM_SECRET |
Database Webhook은 Postgres 행 변경 이후 비동기로 HTTP 요청을 보낸다. 업무 트랜잭션과 푸시 전달을 분리했기 때문에 Expo나 FCM에 장애가 생겨도 할 일과 앱 내부 알림 행은 DB에 남는다.
10. DB 연결만 먼저 검증하기
아직 FCM을 연결하지 않은 상태에서도 다음은 확인할 수 있다.
- Supabase 환경 변수가 들어간 개발 빌드를 두 Android 기기에 설치한다.
- 첫 보호자가 가족을 만든다.
- 두 번째 보호자가 초대 코드로 참여한다.
- 한 기기에서 새 할 일을 만든다.
- 다른 기기에서 앱을 연 상태로 할 일이 나타나는지 본다.
- 아이 완료 요청과 보호자 승인이 DB에 한 번만 기록되는지 본다.
- Supabase Dashboard의 Table Editor와 Logs에서 오류를 확인한다.
이 단계가 성공하면 Auth·RLS·Realtime 경로는 연결된 것이다. 시스템 알림이 아직 안 오는 것은 정상이다. 다음 단계의 EAS projectId와 FCM V1이 없기 때문이다.
11. 자주 생기는 실수
| 증상 | 먼저 볼 곳 |
|---|---|
| 앱이 계속 체험 모드로 열림 | EXPO_PUBLIC_SUPABASE_URL, publishable key가 빌드에 포함됐는지 |
| 보호자 로그인 후 가족 데이터 없음 | 해당 계정의 household_members 연결과 RLS |
| 아이 익명 로그인은 되지만 보호자 RPC도 실행됨 | is_anonymous 검사와 restrictive policy 조합 |
| 이미지 업로드 403 | storage.objects의 INSERT뿐 아니라 필요한 SELECT 정책 |
| Webhook 401 | 두 곳의 PUSH_WEBHOOK_SECRET 값과 헤더 이름 |
| Webhook 200인데 푸시 없음 | 아직 push token·EAS·FCM이 없거나 기기 토큰이 등록되지 않음 |
다음 글 EAS projectId와 FCM V1 이해하기에서는 Expo 프로젝트 식별자와 Google 서비스 계정 키의 차이를 설명하고 Android 원격 알림을 실제 빌드에 연결한다.
참고자료
- Supabase: Local development with schema migrations
- Supabase: Anonymous Sign-Ins
- Supabase: Storage Access Control
- Supabase: Database Webhooks
- Supabase: Edge Function secrets
- 프로젝트 내부 검증 근거:
praise-sticker/supabase/migrations/202608040001_initial.sql,supabase/functions/push/index.ts,scripts/test-local-cloud.mjs
Reader questions
읽으며 남긴 질문
글을 읽으며 붙잡아 둔 질문입니다. 다시 살펴보거나, 보내지 않은 질문을 한 번에 블로그 주인에게 전달할 수 있습니다.