Firebase로 데이터베이스 짱 쉽게 사용하기

y·2024년 6월 19일
4

💬 TIL

목록 보기
9/10
post-custom-banner

프로젝트를 진행하던 중 데이터베이스를 mongoDB를 사용할지 Firebase를 사용할지 고민이 많았다. mongoDB는 여러번 사용해봤고 Firebase는 사진 저장으로 한 번 써본게 다였다.

Firebase를 한 번 써봤지만 제대로 사용해보지 않아 제대로 써보고자 사용하게 되었고, 사용하며 알게된 사실과 쉽게 사용하는 방법을 공유하고자 한다.

Firebase 그렇게 사용하시면 안돼요!


전에 Firebase를 사용할 때 실수를 했었다. 실수를 바탕으로 새롭게 공부했고 내가 했던 실수는 다음과 같다.

1. Firebase의 api 키와 같은 중요한 정보를 NEXT_PUBLIC 변수로 적고, 규칙에는 누구나 접근 가능하게 설정했다

Next.js에서 NEXT_PUBLIC 변수는 클라이언트 컴포넌트에서 사용한다. 그런데 클라이언트 환경변수는 노출될 위험이 있다. 당시 블로그의 Firebase 설명 글에 전부 NEXT_PUBLIC을 붙인 경우 밖에 없었고, 이것이 잘못된 줄 몰랐었다.

❗️ Firebase api 키는 다른 api 키와 다르다

Firebase api 키는 어떤 프로젝트인지 식별하기 위한 용도이므로, api 키가 노출되어도 사실 상관없다. 그러나 이 상태에서 규칙 설정으로 누구나 접근 가능하게 한다면 api 키가 노출되는건 위험하다. 내가 전에 어떤 기술 블로그를 보고 따라한 방법은 api 키를 NEXT_PUBLIC 변수로 저장하고 규칙 설정도 누구나 접근 가능하게 설정한 것이었다.

  1. api 키를 노출하되, 규칙 설정으로 특정 사용자만이 데이터베이스를 읽고 쓰도록 설정한다.
  2. api 키를 노출하지 않고 데이터베이스 연결을 막고, 규칙 설정은 누구나 데이터베이스를 읽고 쓰도록 설정한다.
  • Next.js의 서버 라우트를 이용해 클라이언트 측에서 Firebase 연결이 필요하면 해당 서버 라우트로 요청해 Firebase를 이용하고, 서버 측에서 연결할 때는 그냥 서버 컴포넌트에서 바로 쓰는 방법을 택했다.
  • 밑에서도 나오지만 규칙에서 if true로 설정하면 별도의 인증 없이, 제공되는 api 키와 같은 정보로 데이터베이스에 접근하게 된다. 그래서 NEXT_PUBLIC의 환경변수를 쓰지 않는게 안전하다 판단하였다.

2. 블로그 글을 복붙해서 사용해서 어떻게 작동하는지 모른다

블로그 코드를 복사해와서 붙여넣었기 때문에 코드가 무슨 역할을 하는지도 몰랐고, 데이터베이스에 내가 저장하려는 데이터가 저장되는걸 보고 "오 끝이다.. 나도 사진 저장하는 걸 구현했군 대단해..!" 했었다. 보통 블로그 글을 보면 Firebase 연결 글이 위주고 가끔 데이터를 하나 저장하는 설명이 나오는데 이해하기 부족했다. 원하는 데이터가 데이터베이스에 있는지 확인은 어떻게 하는지, 조건에 따라 검색은 어떻게하는지 등에 대해서 공부해보았다.

Firebase 연결까지 성공했다면, 두번째부터 보면 된다.

첫번째, Firebase 연결하기


1. 설치 및 기본 설정하기

  • 내 프로젝트 폴더에 npm install firebase로 Firebase를 설치해준다.
  • Firebase 사이트로 이동하면 로그인을 하고 프로젝트를 추가하면 된다.

  • 프로젝트를 추가하고 나면, 앱에 Firebase를 추가하여 시작하기가 있다. 나는 웹 프로젝트에서 Firebase를 이용할 것이라 웹을 선택했다.

  • 앱 이름을 입력해주고 나면 위와 같은 코드가 뜬다. 이 코드를 이용해 Firebase에 연결할 것이다. 코드는 복사해놓으면 된다.

  • 이제 데이터베이스를 만들어주기 위해 Firestore Database로 가서, 데이터베이스 만들기를 하면 된다.

  • 참고로 위치는 서울로 선택한다. 한국 거주가 아니라면, 위치를 가까운 곳으로 설정하면 된다.

  • 위치 설정 후 프로덕션 모드를 선택하고 데이터베이스를 만들어준다.

  • 만들어진 Firestore에서 규칙 부분의 코드를 false가 아닌 true로 수정해준다.

2. 데이터베이스 생성하기

  • Firestore의 데이터 탭에 컬렉션 시작 버튼을 누르면 위와 같이 컬렉션 시작이 뜬다. 컬렉션은 데이터베이스 이름이라 생각하면 된다. 이름을 원하는대로 지정해주면 되는데, 만약 유저 정보를 저장하는 데이터베이스를 만든다면 user와 같은 이름으로 지정해준다.

  • 문서 ID는 데이터베이스에 저장되는 하나의 데이터 ID이다. 지정해줄 수도 있지만, ID를 따로 지정할 필요가 없다면 자동 ID를 사용하면 된다.

위에서 마음대로 저장해줘도 된다. 컬렉션 내 생성된 문서는 삭제해주면 되고, 위의 페이지에서 만드는 문서는 예시로 두어도 된다. 우리가 해야할 일은 프로젝트 내에서 데이터베이스에 접근해 데이터를 저장하고 가져오는 일이다.

  • user라는 데이터베이스에 예시데이터가 하나 저장되어있는 것을 볼 수 있다.

3. 데이터베이스 연결하기

프로젝트에서 데이터베이스에 접근해 데이터를 추가하기 위해서는 프로젝트 내에서 Firebase와 연결해줘야한다. 위에서 Firebase API 키 등이 발급된 복사해두었던 코드를 그대로 사용하면 된다.

2개의 파일을 생성해줘야한다. firebase 폴더를 만들어서 해당 파일 2개를 넣어줬다. 위치는 편한 곳에 넣으면 된다.

자바스크립트, 타입스크립트 둘 다 밑의 코드와 같이 작성하면 된다.

app/_firebase/firebaseDB.tsx

import { initializeApp } from "firebase/app";

const firebaseConfig = {
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  projectId: process.env.FIREBASE_PROJECT,
  storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.FIREBASE_MESSAGING_SENDERID,
  appId: process.env.FIREBASE_APP_ID,
  measurementId: process.env.FIREBASE_MEASUREMENT_ID
};

const app = initializeApp(firebaseConfig);
export default app;
  • 위 코드에서 모든 항목은 .env 파일에 저장해서 환경변수로 가져왔다. 해당 키는 Firestore를 처음 만들었을 때 복사해놓은 코드에 다 있다. 이를 .env 파일에 다 적어주면 된다.

app/_firebase/firestore.tsx

import app from "./firebaseDB";
import { getFirestore } from "firebase/firestore";

const fireStore = getFirestore(app);
export default fireStore;

위 두 파일을 생성했다면 연결이 끝이 난다. 이제 Firebase에서 생성한 데이터베이스에 데이터를 저장하고, 수정하고 삭제하는 등의 원하는 작업을 할 수 있다.

두번째, Firebase 이용하기


데이터 추가하기

예시로 위에서 만든 user 데이터베이스에 유저 정보를 저장해보자.

import fireStore from "firestore.tsx 파일 위치";
import { collection, addDoc } from "firebase/firestore";

const [name, age] = ["신짱구", 5];
const addData = async() => {
  await addDoc(collection(fireStore, "user"), {
    user_name: name,
    user_age: age
  });
}

addData();
  • 데이터를 저장할 때 addDoc()을 사용한다.
  • addDoc(collection(연결할 firestore, 데이터베이스 이름), { 저장할 데이터 }) 형태로 사용한다.
  • 저장할 데이터에는 원하는 형식을 넣어주면 된다.

데이터베이스 조건문 조회

조건문을 사용해 데이터베이스에 특정 조건을 만족하는 데이터를 조회해볼 수 있다.

조건문을 사용하기 위해서는 where()을 이용하면 된다. where(검색할 필드, 비교 연산, 값)을 이용해 쿼리를 만들면 된다. user 데이터베이스에서 나이가 5살인 데이터가 있는지 조회해보자.

import fireStore from "firestore.tsx 파일 위치";
import { collection, query, where, getDocs } from "firebase/firestore";

const searchData = async() => {
  const q = query(collection(fireStore, "user"), where("user_age", "==", 5);
  const querySnapshot = await getDocs(q);
  
  // querySnapshot을 출력해서 보면 나이가 5인 데이터가 있다
}

searchData();

조건문 조회 시 데이터가 없는 경우

조건에 맞춰 검색했는데 데이터가 없을 수 있다. 그 때는 querySnapshot이 null, false 등과 같은 값이 아니라 여전히 객체 형태로 반환되기 때문에 데이터가 없는지 확인하려면 querySnapshot.empty를 이용하면 된다. 해당 값이 true라면 데이터베이스에 해당 조건에 일치하는 데이터가 없다는 뜻이다.

이 외의 이용방법

데이터를 수정하기 위해서는 setDoc, 데이터를 삭제하기 위해서는 deleteDoc이 있으며 이 외에도 다양하게 있다. 모든 부분을 포스팅하는데는 어려움이 있어 이를 설명해둔 공식문서(한국어로 볼 수 있다)를 참고하면 원하는 작업을 할 수 있을 것이다.

이 외에도 다양하게 있으니 공식 문서를 살펴보며 필요한 것을 찾아 적용해보면 된다.

+) 클라이언트 컴포넌트에서 Firebase 사용하기


클라이언트에서 사용하기 위해 내가 사용한 로직은 다음과 같다. 참고로 나는 리액트 쿼리도 함께 사용했는데, 리액트 쿼리를 사용하지 않아도 로직은 비슷하다.

리액트 쿼리를 사용하지 않고 fetch를 이용한다면 fetch를 요청할 때, 요청 주소를 서버 라우트 주소를 넣어주면 된다.

코드 살펴보기

코드랑 보면 좀 더 이해하기 쉬울거 같아 코드를 가져와봤다.

클라이언트 컴포넌트

export const Submit = () => {
  const { mutate, isPending } = useMutation({
    mutationFn: () => writePost({
      title: '제목',
      content: '내용'
    }), 
    onSuccess: (data) => {
      console.log(data);
    },
    onError: (error) => {
      console.log(error);
    },
  })

  const registerPost = () => {
    mutate();
  }

  return (
    <div>
      <button onClick={ registerPost }>게시물 작성하기</button>
    </div>
  );
}
  • 게시물 작성하기 버튼을 누르면, registerPost 함수가 실행된다.

  • mutate()가 실행되며 writePost를 호출하는데, 저장할 포스트 내용인 title, content를 전달해준다.

writePost 함수

writePost 함수는 위 코드에 작성할 수도 있지만, 요청하는 함수를 한 파일에 모아두었다. 어디 정의하든 개발자의 마음이니 더 효율적인 방법을 택하면 된다.

export const writePost = async(post) => {
  const response = await axios.post(`${process.env.NEXT_PUBLIC_BASE_URL}/api/post/register`, { post }, {
    headers: {
      'Content-Type': 'application/json'
    }
  });
  return response;
}
  • 클라이언트 컴포넌트에서 전달 받은 포스트 내용을 받아와, api/post/register로 요청하면 된다.

  • 이때 api 주소는 본인이 생성한 주소로 적으면 되고, 로컬에서 개발 중이라면 /api 앞에 http://localhost:3000을 붙여줘야한다. 나중에 배포하면 이 주소도 바뀔 것이고 수정해야하므로 환경 변수로 정의했다.

서버 라우트

  • writePost에게 받은 요청을 처리해준다. 즉, Firebase 데이터베이스에 데이터를 저장하는 명령을 여기 적으면 되는 것이다.

  • app 폴더에 api를 만들어도 되고, pages에 api를 만들어도 된다. 그러나 둘의 사용법이 약간 다르다. pages 방법은 인터넷에 많은 사람들이 사용하고 있어 참조할게 많기 때문에 최신 버전인 app/api로 만들었다.

  • api 경로를 설정하려면 api/원하는 경로의 폴더/폴더 이런식으로 폴더를 생성해주고 마지막에 파일 이름은 무조건 route.js/route.ts로 설정해주면 된다.

import { NextRequest, NextResponse } from "next/server";
import { addDoc, collection } from "firebase/firestore";
import fireStore from "@/app/_firebase/firestore";

export async function POST(request :NextRequest) {
  const body = await request.json();
  const data = body.post; // post로 전송했기 때문에 body.post에 내가 전송한 데이터가 저장되어있다

  try {
    const response = await addDoc(collection(fireStore, "Post"), {
      title: data.title,
      content: data.content
    });
    return NextResponse.json('성공');
  } catch (error) {
    console.error('Error adding document: ', error);
    return NextResponse.json('실패');
  }
};
  • route 파일에는 해당 api가 post 처리를 위한 것이면 POST로 함수 이름을 정하고, get 처리를 위한 것이면 GET 함수 이름으로 정의해주면 된다.

  • addDoc을 이용해 Post라는 데이터베이스에 새로운 포스트를 저장했다.

간단하게 데이터베이스를 저장하는 로직을 알아보았다. 저장하고 난 상태가 궁금하면 리액트 쿼리에서 요청할 때 onSuccess 부분에서 onSuccess: (data) => { console.log(data); }를 이용해 결과를 확인해보거나, Firebase 데이터베이스에 직접 가서 봐도 된다.

profile
hiyunn.contact@gmail.com
post-custom-banner

0개의 댓글