firebase 사용해보기

최경락 (K_ROCK_)·2022년 7월 22일
1

firebase?

  • firebase 란 간단하게, 구글에서 제공하는 개발 플랫폼으로, 인증, 데이터베이스 등의 기능을 자동으로 구축시켜주는 서비스이다.
  • 원래라면 백엔드에서 복잡하게 구현해야 할 서버와 DB를 제공하며, 이를 이용하면 프론트엔드도 서버의 구현없이 자신의 프로젝트에 DB를 적용 시킬 수 있다고 생각하면 된다.

시작하기

프로젝트 생성

  • 먼저 firebase 의 웹사이트로 이동하여 로그인을 한 뒤, 새로운 프로젝트를 만든다.

  • 프로젝트 추가를 눌러 프로젝트의 이름을 설정하고, 몇가지의 설정을 끝 마치면 위처럼 프로젝트가 생성된다.
    → 여기서 사용해 볼 것은 firebasePractice 프로젝트이다.

서비스 추가

  • 해당 프로젝트를 클릭하고, 홈화면에 표시되는 아래의 내용을 이용하여 웹 앱을 추가하자.

→ 나는 웹에서 사용할 것이므로 </> 아이콘을 클릭했다.

  • 이름을 등록하고, 다음을 누르면 SDK 를 추가하는 방법에 대해 안내해주며, 이를 따른다.

  • 위의 정보는 외부로 알려지면 곤란한 정보이니, .env 를 이용하여 환경변수로 작성해주었다.
import { initializeApp } from 'firebase/app';

const firebaseConfig = {
  apiKey: process.env.REACT_APP_APIKEY,
  authDomain: process.env.REACT_APP_AUTH_DOMAIN,
  projectId: process.env.REACT_APP_PROJECT_ID,
  storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_APP_ID,
  measurementId: process.env.REACT_APP_MEASUREMENT_ID,
  databaseURL: process.env.REACT_APP_DATABASE_URL,
};

const app = initializeApp(firebaseConfig);
  • 이를 하나의 파일에서 관리하고 내보내기 위해, 프로젝트의 src / firebase / firebase.ts 에 작성해주었다.

데이터 베이스 연동

데이터 베이스 연결 (Firestore)

  • Firestore를 사용하여 데이터 베이스를 연결해보고자 한다.
    → firebase 가 제공하는 db 는 두 가지로, 실시간 데이터베이스파이어스토어 데이터베이스가 있다.
  • 위의 firebase.ts 파일에 아래의 코드를 추가했다.
import { getFirestore } from 'firebase/firestore';

// ... 기존내용

const firestore = getFirestore(app);
export const firebaseDB = firestore;
  • 이를 firebaseDB 라는 이름으로 내보내어 db에 접근 할 수 있게 됐다.

함수 작성하기

  • 필요한 곳에서 db에 접근하는 로직을 작성해도 무관이야 하겠지만, 각 컴포넌트의 로직이 복잡해 질 가능성이 크다.
  • 그래서 같은 폴더에 DBHandler 라는 파일을 만들어 객체 형식으로 메소드를 관리를 하기로 했다.
  • 게시물 수정을 제외한 읽기, 쓰기, 삭제를 구현했다.
  • 아래의 코드들은 전부 공식문서를 참고 했다.
import {
  collection,
  deleteDoc,
  doc,
  getDocs,
  setDoc,
} from 'firebase/firestore';
import { firebaseDB } from './firebase';

// collection(db, 'collectionPath') 가 접근하고자 하는 db 와 컬렉션의 주소를 가짐
// REST API 를 기준으로 하자면, URI 의 역할을 하는 것.

const DBHandler = {
  async readPost(ref: string) {
    const snapshot = await getDocs(collection(firebaseDB, ref));
    return snapshot.docs.map((doc) => {
      return Object.assign(doc.data(), {id : doc.id});
    })
  },
  async writePost(
    ref: string,
    data: { nickname: string; content: string; userId: string }
  ) {
    await setDoc(doc(collection(firebaseDB, ref)), {
      nickname: data.nickname,
      content: data.content,
      userId: data.userId,
      timestamp: serverTimestamp(),
      comments: [],
    });
  },
  async deletePost(id: string) {
      deleteDoc(doc(firebaseDB, `posts/${id}`));
    },
  async writeComment(id: string, data: { comment: string; userId: string }) {
    await updateDoc(doc(firebaseDB, `posts/${id}`), {
      comments: arrayUnion({ content: data.comment, userId: data.userId }),
    });
  },
  async deleteComment(id: string, data: { comment: string; userId: string }) {
    await updateDoc(doc(firebaseDB, `posts/${id}`), {
      comments: arrayRemove({ content: data.comment, userId: data.userId }),
    });
  },
};

export default DBHandler;
  • Firestore의 CRUD 를 REST Api의 메소드와 연관지어 보면 아래와 유사할 것이다.
    • GET - getDocs()
    • POST - setDoc() / addDoc()
    • UPDATE - updateDoc()
    • DELETE - deleteDoc()

읽기 (1회성)

  • 원하는 데이터를 읽기 위해서는 getDoc() 메소드를 사용한다.
  async readPost(ref: string) {
    const snapshot = await getDocs(collection(firebaseDB, ref));
    return snapshot.docs.map((doc) => {
      return Object.assign(doc.data(), {id : doc.id});
    })
  },
  • collection(db, collectionPath) 를 이용하여 원하는 콜렉션의 참조값을 저장하고, 해당 참조값을 getDocs()에 전달한다.
  • 이때 getDocs() 이 반환하는 값은 프라미스 객체이며, 해당 객체에 docs 프로퍼티를 사용하여 배열의 형태로 컬렉션 내부의 데이터 객체를 받을 수 있다.
  • 전달 받은 배열을 이용하여 각 요소에 data() 메소드를 이용하면 우리가 직접적으로 데이터를 꺼내 쓸 수 있는 객체의 형태가 된다.
    → 나는 데이터의 id 값이 필요하여 데이터와 id 를 병합시킨 객체를 반환시켰다.

읽기 (변경 추적)

  • 최초엔 위의 로직을 사용했지만, 컬렉션의 변화에 따라 상태를 추적 할 수 없다는 단점이 존재했다.
    → 서버의 데이터가 변경되어도 해당 내용을 바로 화면에 반영 시킬 수 없다.
  • 공식문서를 참고하니, onSnapshot() 을 이용하여 서버의 변경을 감지 할 수 있다고 하여 사용했다.
  • 다만, 해당 서버가 업데이트 될 때 마다 상태를 업데이트 시켜줘야 했기 때문에, DBHandler 에 추가하지 않고, App 컴포넌트에 직접 작성해주었다.
// App.tsx

const [posts, setPosts] = useState<any[]>([]);
// 서비스 제작보단 파이어베이스 경험이 우선이다보니 any 타입이 좀 많다.

useEffect(() => {
    onSnapshot(collection(firebaseDB, 'posts'), (snapshot) => {
      const postsArr = snapshot.docs.map((eachDoc) => {
        return Object.assign(eachDoc.data(), { id: eachDoc.id });
      });
      const sortedArr = postsArr.sort((a: any, b: any) => {
        return b.timestamp - a.timestamp;
      });

      setPosts(sortedArr);
    });
  • onSnapshot(collectionRef, callback) 으로 사용하며, 콜백함수의 매개변수로 snapshot 을 이용 할 수 있다.
    collectionRef == collection(db, collectionPath)
  • getDocs()snapshot 과는 다르게 프라미스 객체를 반환하지 않으며, 바로 docs 프로퍼티를 사용 할 수 있다.
    getDocs() 의 경우 async/await 문법이 아니면 docs 에 접근 할 수 없다.
  • 여기서 전달받은 데이터 배열을 저장하고, 정렬하여 상태를 업데이트 하는 로직을 작성하였고, 서버의 업데이트에 따라 상태도 자동으로 갱신되는 것을 확인 할 수 있었다.

쓰기

  • 데이터를 추가하기 위해서는 setDoc() 메소드를 사용한다.
  async writePost(
    ref: string,
    data: { nickname: string; content: string; userId: string }
  ) {
    await setDoc(doc(collection(firebaseDB, ref)), {
      nickname: data.nickname,
      content: data.content,
      userId: data.userId,
      timestamp: serverTimestamp(),
      comments: [],
    });
  },
  • setDoc(doc(collectionRef), data, data_id) 로 원하는 컬렉션에 원하는 id 를 설정하여 데이터를 추가 할 수 있다.
  • 위에서는 id 필드를 작성하지 않았는데, 이 경우에는 파이어베이스에서 자동으로 id 를 생성한다.
  • id 를 따로 작성할 필요가 없이, 전적으로 자동으로 생성되게 하고 싶은 경우에는 addDoc() 을 사용 할 수 있다.
  • addDoc() 을 사용한 경우의 코드는 아래와 같다.
  async writePost(
    ref: string,
    data: { nickname: string; content: string; userId: string }
  ) {
    await addDoc(collection(firebaseDB, ref), {
      nickname: data.nickname,
      content: data.content,
      userId: data.userId,
      timestamp: serverTimestamp(),
      comments: [],
    });
  },
  • doc() 이 사용되지 않고, 바로 컬렉션과 데이터만 전달한다.
  • 여기서 doc() 은 해당 콜렉션에 있는 문서를 나타낼 것이고, setDoc 은 해당 문서에 데이터를 추가한다 는 뜻으로 볼 수 있고, addDoc 은 해당 콜렉션에 데이터를 추가한다 라고 볼 수 있을 것 같다.
  • 이들의 차이점은 해당 컬렉션 주소의 문서가 메인이 되는가 혹은, 컬렉션이 메인이 되는가 의 차이이지 않을까 추측해본다.

삭제

  • deleteDoc() 을 이용한다.
async deletePost(id: string) {
    deleteDoc(doc(firebaseDB, `posts/${id}`));
  },
  • 비교적 간단하다.
  • 삭제하기를 원하는 데이터베이스와 콜렉션 주소를 doc() 의 인자로 작성하여 전달하면 된다.

수정

  • 위에는 없지만 간단한 내용이라 작성해본다.
  • 데이터의 수정에는 updateDoc() 을 사용한다.
  • 아래의 코드는 공식문서를 참고했다.
async updatePostContent(id:string, content : string){
  updateDoc(doc(firebaseDB, `posts/${id}`),{
    content
  })
},
  • deleteDoc() 과 상당히 유사하며, 수정하고자 하는 필드와 변경하고자 하는 데이터를 전달하면 된다.

+

참고 블로그
https://velog.io/@wiostz98kr/Firebase-Realtime-Database

https://velog.io/@dev-hannahk/react-firebase-crud
https://velog.io/@seondal/Firebase-v9부터-달라진-인증모듈-사용법

0개의 댓글