Firestore CRUD 사용법: 컬렉션 구조와 읽기 쓰기 흐름

··설명 약 7분·작성: 해비·블로그 소개

Firestore에서 반환된 문서 ID로 생성·조회·수정·삭제를 어떻게 이어가나요?

Firestore는 컬렉션 안의 문서를 경로와 ID로 읽고 씁니다. 메모 하나를 생성한 뒤 실제 반환 ID로 읽기·수정·삭제를 이어가며 각 요청의 완료를 기다리세요.

적용 조건·범위: 목록이 커지면 limit와 커서 페이지네이션을 추가합니다. 복합 조건·정렬에 필요한 인덱스를 확인하세요. 여러 문서가 같이 바뀌어야 하면 batch나 transaction을 검토합니다. 단일 문서 삭제가 하위 데이터를 지우지 않으므로 계정 탈퇴 정리는 별도 범위입니다. 선택 필드에 undefined를 넣지 말고 저장할 값을 명시적으로 구성합니다.

본문 근거: firebase.google.com/docs/firestore/manage-data/add-data · firebase.google.com/docs/firestore/manage-data/delete-data · firebase.google.com/docs/firestore/security/rules-query

학습 목표와 사전 지식

목표: 컬렉션·문서 경로와 CRUD 요청의 의미를 한 메모 데이터 흐름으로 연결합니다.
사전 지식: JavaScript 모듈/async, Firebase 초기화·로그인

Firestore는 컬렉션 안에 문서를 저장합니다. 문서 ID와 필드의 사용자 UID는 다른 값이며, 읽기·쓰기 호출을 제공하는 SDK와 그 호출을 허용하는 Rules는 함께 설계해야 합니다.

경로·호출 의미
notes 컬렉션
notes/자동ID 문서
addDoc 새 자동 ID 생성; 재시도하면 중복 생성 가능
setDoc 지정 ID 쓰기; 기본은 덮어쓰기, merge 선택 가능
updateDoc 기존 문서 일부 수정; 없는 문서는 실패
deleteDoc 해당 문서 삭제; 하위 컬렉션 자동 삭제 아님

src/notes.js · 전체 학습 모듈; 초기화된 db와 인증 관찰이 끝난 user 전달

import { addDoc, collection, deleteDoc, doc, getDoc, getDocs,
  query, updateDoc, where } from 'firebase/firestore';

export async function createNote(db, user, text) {
  if (!user) throw new Error('로그인이 필요합니다.');
  const created = await addDoc(collection(db, 'notes'), { ownerId: user.uid, text });
  return created.id;
}
export async function readNote(db, id) {
  const snapshot = await getDoc(doc(db, 'notes', id));
  return snapshot.exists() ? { id: snapshot.id, ...snapshot.data() } : null;
}
export async function listMyNotes(db, user) {
  if (!user) throw new Error('로그인이 필요합니다.');
  const result = await getDocs(query(collection(db, 'notes'), where('ownerId', '==', user.uid)));
  return result.docs.map((item) => ({ id: item.id, ...item.data() }));
}
export async function editNote(db, id, text) {
  await updateDoc(doc(db, 'notes', id), { text });
}
export async function removeNote(db, id) {
  await deleteDoc(doc(db, 'notes', id));
}

단독 실습의 준비물은 Firebase 초기화 구조: Next.js firebase.ts 설계 기준에서 만든 db, Firebase Authentication 사용 기준: 로그인 유지와 비밀번호 재설정에서 최초 인증 관찰이 끝난 user, Firebase permission-denied 오류 해결: Firestore Rules 체크리스트의 소유자 notes 규칙입니다. 아래 생성→조회→수정→삭제 함수는 이 입력들을 전달받으며 이전 TaskFlow 앱의 데이터나 고정 문서 ID를 사용하지 않습니다.

규칙과 문서 구조를 맞추기

이 모듈의 notes는 ownerId와 text만 사용하며 permission-denied 해결 글의 notes 규칙과 대응합니다. UI의 user 인자 검사는 사용자 안내이고 서버의 Rules가 실제 소유권을 검증합니다. 사용자 A의 문서 ID를 B가 알아도 읽기·수정·삭제가 거부되어야 합니다. 목록 조회에는 같은 ownerId 조건을 붙여 전체 컬렉션을 무작정 읽지 않습니다.

Firestore 컬렉션 문서 ID 필드 쿼리 인덱스 설계 구조

완료 응답 이후 다음 작업하기

const id = await createNote(db, user, "첫 메모")로 실제 반환 ID를 보관하고 readNote(db, id), editNote(db, id, "수정"), removeNote(db, id) 순서로 await합니다. 예시 ID를 가정해 다른 문서를 수정하지 마세요. 없어진 문서를 읽는 결과와 권한 거부 예외는 구분합니다. 읽기 함수가 null을 반환하는 경우는 허용된 요청에서 snapshot.exists()가 false인 경우입니다.

다음 단계: 쿼리와 정합성

목록이 커지면 limit와 커서 페이지네이션을 추가합니다. 복합 조건·정렬에 필요한 인덱스를 확인하세요. 여러 문서가 같이 바뀌어야 하면 batch나 transaction을 검토합니다. 단일 문서 삭제가 하위 데이터를 지우지 않으므로 계정 탈퇴 정리는 별도 범위입니다. 선택 필드에 undefined를 넣지 말고 저장할 값을 명시적으로 구성합니다.

Firestore 컬렉션과 문서 기준으로 CRUD 액션이 나뉘는 구조

개발 프로젝트 확인 순서

A 계정으로 메모 하나 생성→ID로 읽기→수정→목록 확인→삭제를 실행합니다. 각 Promise 실패는 UI에서 catch하여 메시지를 표시합니다. B 계정과 비로그인 상태에서 A의 ID로 동일 호출을 해 거부를 확인합니다. 이 글은 CRUD 개념 모듈이며 로그인 화면·프로젝트 생성·클라우드 검증을 끝낸 완성 앱으로 제시하지 않습니다.

한 개의 메모로 CRUD 확인하기

이 절차는 웹 앱에서 Firestore db와 로그인 완료된 Firebase Auth user를 이미 얻었고, notes/{id}에 대해 소유자만 읽고 쓸 수 있는 Security Rules를 배포했다는 전제입니다. src/notes.js의 함수를 가져와 아래 함수를 호출하세요. Firestore 연결이나 인증 초기화 코드는 이 편에 포함하지 않습니다.

import { createNote, readNote, listMyNotes, editNote, removeNote } from './notes.js';

export async function checkNoteFlow(db, user) {
  const id = await createNote(db, user, '첫 메모');
  const created = await readNote(db, id);
  const inList = (await listMyNotes(db, user)).some((note) => note.id === id);
  await editNote(db, id, '수정한 메모');
  const edited = await readNote(db, id);
  await removeNote(db, id);
  const removed = await readNote(db, id);
  return { id, createdText: created?.text, inList, editedText: edited?.text, removed };
}

로그인한 A 계정에서 checkNoteFlow(db, user)의 반환값을 확인하세요. createdText는 ‘첫 메모’, inList는 true, editedText는 ‘수정한 메모’, removed는 null이어야 합니다. id는 실행할 때마다 달라집니다. 오류가 나면 어느 await에서 실패했는지와 Firestore 오류 코드를 기록하세요. 생성 후 실패했다면 남은 시험 문서를 직접 정리하세요.

소유권을 확인하려면 A 계정에서 별도 시험 문서를 createNote(db, A_user, '권한 검사')로 만들고 ID를 기록하세요. B 계정으로 전환해 readNote(db, A의_id)를 호출한 다음, A 계정으로 돌아와 시험 문서를 삭제하세요. 소유자 전용 Rules가 정확히 적용됐다면 permission-denied로 거부되어야 합니다. B의 listMyNotes(db, B_user)에는 A의 문서가 나타나지 않아야 합니다. readNote/editNote/removeNote는 user를 인자로 받지 않으므로 UI의 로그인 검사만으로 보호되지 않습니다. 실제 접근 판정은 배포된 Rules가 합니다. 허용된 조회에서 문서가 없을 때의 null과 권한 거부 예외를 혼동하지 마세요.

Rules가 소유자 필드를 기준으로 목록 접근을 제한한다면 where('ownerId', '==', user.uid) 조건이 필요합니다. Firestore Rules는 목록 결과를 사후 필터링하지 않으므로 조건이 맞지 않는 쿼리 전체가 거부될 수 있습니다. 실제 Firebase 프로젝트 또는 Emulator에서 A/B 계정으로 확인하기 전에는 통과했다고 표시하지 않습니다.

스스로 바꿔 보기: createNote의 ownerId를 다른 UID로 보내면 어떤 단계에서 거부되어야 할까요? create 규칙이 request.auth.uid와 request.resource.data.ownerId의 일치를 요구해야 합니다. 편집 때 ownerId 변경도 금지하는 규칙이 없다면, 이 모듈만으로 소유권 불변을 보장할 수 없습니다.

직접 확인할 결과

모듈 node --check
A/B/비로그인 CRUD 절차 제공
실제 CRUD·Rules 미실행

검증 범위와 기준일

공식 문서 확인일: 2026-09-13. 웹 SDK 예제는 모듈형 API 기준입니다. 이 글의 코드·구조 정적 검토와 실제 클라우드 인증·저장·배포 검증을 구분합니다. 본인 개발 Firebase 프로젝트의 실제 인증·권한·배포 요청은 이 편집 환경에서 실행하지 않았습니다.

이어서 학습하기

공식 문서

이 글이 도움이 되었나요?

조회 중

읽음 기록

읽음 — / 전체 20개 · 필수 14개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기