์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

[REACT] React + Typescript + Vite + SCSS๋กœ ๋งŒ๋“œ๋Š” ์‡ผํ•‘๋ชฐ ๊ฐœ์ธํ”„๋กœ์ ํŠธ (9/10)

์˜ˆ๊ธ€ 2024. 1. 28. 19:42

๐Ÿ’ก์˜ค๋Š˜ ํ•  ๊ฒƒ!

- firebase ์—ฐ๋™

 

1๏ธโƒฃ firebase ์„ค์น˜

๐Ÿ“š firebase๋ž€?

  • NoSQL ๊ธฐ๋ฐ˜์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
  • RTSP ๋ฐฉ์‹์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ง€์› 
    • RTSP : Real Time Stream Protocol. ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ์ดํ„ฐ๋“ค์„ ์ „์†ก

 

1. firebase ์‹œ์ž‘ํ•˜๊ธฐ

2. ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ

3. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งŒ๋“ค๊ธฐ - Firestore Database

4. ์ปฌ๋ž™์…˜ ๋งŒ๋“ค๊ธฐ

 

5. ํ”„๋กœ์ ํŠธ ์„ค์ • / ํ”Œ๋žซํผ ์„ ํƒ

- ์•ฑ ๋“ฑ๋ก ํ›„ Firebase SDK ์ถ”๊ฐ€

 

6. firebase ์„ค์น˜

- ํ„ฐ๋ฏธ๋„์—์„œ server๋กœ ์ด๋™

npm install firebase

 

7. SDK์ž‘์„ฑ (๋ณด์•ˆ์„ ์œ„ํ•ด .env ํŒŒ์ผ ์ƒ์„ฑ ํ›„ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ)

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import "dotenv/config";
import { getFirestore } from "firebase/firestore";

// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: process.env.fb_apiKey,
  authDomain: process.env.fb_authDomain,
  projectId: process.env.fb_projectId,
  storageBucket: process.env.fb_storageBucket,
  messagingSenderId: process.env.fb_messagingSenderId,
  appId: process.env.fb_appId,
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
export default app;

export const db = getFirestore(app);

 

2๏ธโƒฃ Product ์—ฐ๋™

1. Query

const productResolver: Resolver = {
  Query: {
    products: async (parent, { cursor = "", showDeleted = false }) => {
      const products = collection(db, "products"); 
      const queryOptions = []; // orderby Error with startAfter, where
      if (cursor) {
        const snapshot = await getDoc(doc(db, "products", cursor)); 
        queryOptions.push(startAfter(snapshot)); 
      }
      if (!showDeleted) queryOptions.unshift(where("createdAt", "!=", null));
      const q = query(products, ...queryOptions, limit(PAGE_SIZE)); 
      const snapshot = await getDocs(q);
      const data: DocumentData[] = [];
      console.log(snapshot);
      snapshot.forEach(
        (
          doc 
        ) =>
          data.push({
            id: doc.id,
            ...doc.data(),
          })
      );
      return data;
    },

    product: async (parent, { id }) => {
      const snapshot = await getDoc(doc(db, "products", id));
      return {
        ...snapshot.data(),
        id: snapshot.id,
      };
    },
  },
 }

 

โœ๏ธ collection : firebase์—์„œ ๋งŒ๋“  ์ปฌ๋ž™์…˜ ์ค‘ products ์ปฌ๋ž™์…˜์„ ๊ฐ€์ ธ์˜ด

โœ๏ธ doc(), getDoc()

  • doc() : ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ฐธ์กฐํ•˜๋ฉฐ, ์ปฌ๋ž™์…˜ ์ด๋ฆ„, ๋ฌธ์„œ ID๋ฅผ ์ธ์ˆ˜๋กœ ์‚ฌ์šฉ
    • ๋”ฐ๋ผ์„œ, cursor(ํŽ˜์ด์ง€ ๋ ์ƒํ’ˆ ID)์™€ ์ „๋‹ฌ
  • getDoc() : doc() ๋ฉ”์„œ๋“œ์—์„œ ์–ธ๊ธ‰ํ•œ ์ปฌ๋ž™์…˜ ๊ธฐ๋ฐ˜ ์ฐธ์กฐ์—์„œ ํŠน์ • ๋ฌธ์„œ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ด
    • promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ๊ทธ ์•ž์— await ํ‚ค์›Œ๋“œ ์ถ”๊ฐ€ 

โœ๏ธ startAfter : ์‹œ์ž‘์ ์„ ์ œ์™ธํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜

โœ๏ธ ๋งŒ์•ฝ ์‚ญ์ œ๋œ ์ƒํ’ˆ(!showDeleted)์ด๋ผ๋ฉด ๋ฐฐ์—ด์—์„œ where๋ฌธ ์กฐ๊ฑด์— ๋งŒ์กฑํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ unshift.

  • where("createdAt", "!=", null) : createdAt์ด ์—†๋Š” ๋ฐ์ดํ„ฐ

โœ๏ธ query : ๋ฌธ์„œ ID๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ถฉ์กฑํ•˜๋Š” ๋ชจ๋“  ๋ฌธ์„œ ๊ฒ€์ƒ‰ (์˜ค๋ฆ„์ฐจ์ˆœ)

  • orderBy()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ์˜ ์ •๋ ฌ ์ˆœ์„œ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Œ
  • limit()์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰๋˜๋Š” ๋ฌธ์„œ์˜ ์ˆ˜๋ฅผ ์ œํ•œํ•  ์ˆ˜ ์žˆ์Œ

โœ๏ธ snapshot.forEach : ๋ฐ์ดํ„ฐ ๊ฐ๊ฐ์˜ id์™€ ๊ฐ’์„ data ๋ฐฐ์—ด์— ์ตœ์ข…์ ์œผ๋กœ ์ €์žฅ

 

2. Mutation

1) addProduct

  • ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณด๋‚ด์˜จ ๋ฐ์ดํ„ฐ ์ •๋ณด๋“ค์„ newProduct ๊ฐ์ฒด์— ์ €์žฅ.
  • addDoc : Create๊ธฐ๋Šฅ์€ addDoc ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉ.
    • addDoc("ํ‚ค๋ฅผ ํ†ตํ•ด ๊ฐ€์ ธ์˜จ db", "payload")
addProduct: async (parent, { imageUrl, price, title, description }) => {
      const newProduct = {
        imageUrl,
        price,
        title,
        description,
        createdAt: serverTimestamp(),
      };

	// products ์ปฌ๋ž™์…˜์— newProduct ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
      const result = await addDoc(collection(db, "products"), newProduct);
      const snapshot = await getDoc(result);
      return {
        ...snapshot.data(),
        id: snapshot.id,
      };
    },

2) updateProduct

  • doc : ์—…๋ฐ์ดํŠธํ•  ๋ฐ์ดํ„ฐ๋ฅผ doc ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ€์ ธ์˜ด
  • ๋งŒ์•ฝ ์—…๋ฐ์ดํŠธํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์—๋Ÿฌ ๋ฐ˜ํ™˜
  • updateDoc: ๋‘๋ฒˆ์งธ ๊ฐ’์œผ๋กœ ๊ธฐ์กด์— ์žˆ๋Š” key๊ฐ’์„ ๋„ฃ๊ณ  value๋ฅผ ๋„ฃ์œผ๋ฉด ๊ธฐ์กด ๊ฐ’์ด ๋Œ€์ฒด๋˜๊ณ , ์—†๋Š” key์™€ value๋ฅผ ๋„ฃ์œผ๋ฉด key, value๊ฐ€ ์ถ”๊ฐ€.
    • data: ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณด๋‚ธ ๋ณ€๊ฒฝ๋œ ๋ฐ์ดํ„ฐ ์ •๋ณด.
updateProduct: async (parent, { id, ...data }) => {
      const productRef = doc(db, "products", id);

      if (!productRef) throw new Error("์ƒํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค.");

      await updateDoc(productRef, {
        ...data,
        createdAt: serverTimestamp(),
      });
      
      const snap = await getDoc(productRef);
      return {
        ...snap.data(),
        id: snap.id,
      };
    },

3) deleteProduct

  • ์‚ญ์ œ๋œ ์ƒํ’ˆ์ด๋ผ๋Š” ํ‘œ์‹œ๋งŒ ํ•ด์ฃผ๊ธธ ์›ํ–ˆ์œผ๋ฏ€๋กœ, updateDoc์„ ํ†ตํ•ด createdAt ๊ฐ’๋งŒ ์‚ญ์ œ์‹œ์ผœ์„œ ์ผ๋ฐ˜ ์ƒํ’ˆ๊ณผ ์‚ญ์ œ ์ƒํ’ˆ์„ ๊ตฌ๋ถ„
deleteProduct: async (parent, { id }) => {
      // ์‹ค์ œ db์—์„œ delete๋ฅผ ํ•˜๋Š” ๋Œ€์‹ , createdAt์„ ์ง€์›Œ์ค€๋‹ค.
      const productRef = doc(db, "products", id);
      if (!productRef) throw new Error("์ƒํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค.");
      await updateDoc(productRef, { createdAt: null });
      return id;
    },

3๏ธโƒฃ Cart ์—ฐ๋™

1. query

const cartResolver: Resolver = {
  Query: {
    cart: async (parent, args) => {
      const cart = collection(db, "cart");
      const cartSnap = await getDocs(cart);
      const data: DocumentData[] = [];
      cartSnap.forEach((doc) => {
        const d = doc.data();
        data.push({
          id: doc.id,
          ...d,
        });
      });
      return data;
    },
  },
 }

 

2. Mutation

1) addCart

  • ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์ถ”๊ฐ€ํ•  ์ƒํ’ˆ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
  • cart ์ปฌ๋ž™์…˜์— ์ถ”๊ฐ€ํ•œ ์ƒํ’ˆ๋ฐ์ดํ„ฐ์™€ ๊ฐ’์ด ๊ฐ™์€ ๊ฒƒ์ด ์žˆ๋Š”์ง€ ํŒ๋ณ„
  • ๋งŒ์•ฝ ๊ฐ™์€ ์ƒํ’ˆ์ด ์žˆ๋‹ค๋ฉด?
    • amount: increment(1): ์ˆ˜๋Ÿ‰ 1์”ฉ ์ฆ๊ฐ€ 
  • ๋งŒ์•ฝ ๊ฐ™์€ ์ƒํ’ˆ์ด ์—†๋‹ค๋ฉด?
    • ์ˆ˜๋Ÿ‰ 1๋กœ ์ €์žฅ, ํ•ด๋‹น ๋ฐ์ดํ„ฐ ์ €์žฅ.
addCart: async (parent, { productId }) => {
      if (!productId) throw Error("์ƒํ’ˆid๊ฐ€ ์—†๋‹ค!");
      const productRef = doc(db, "products", productId);
      const cartCollection = collection(db, "cart");
      const exist = (
        await getDocs(query(cartCollection, where("product", "==", productRef)))
      ).docs[0];

      let cartRef;
      if (exist) {
        cartRef = doc(db, "cart", exist.id);
        await updateDoc(cartRef, {
          amount: increment(1),
        });
      } else {
        cartRef = await addDoc(cartCollection, {
          amount: 1,
          product: productRef,
        });
      }
      const cartSnapshot = await getDoc(cartRef);
      return {
        ...cartSnapshot.data(),
        product: productRef,
        id: cartSnapshot.id,
      };
    },

 

2) updateCart

  • ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณ€๊ฒฝ๋œ amount๊ฐ’์„ ์ €์žฅ
updateCart: async (parent, { cartId, amount }) => {
      if (amount < 1) throw Error("1 ์ดํ•˜๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");

      const cartRef = doc(db, "cart", cartId);

      if (!cartRef) throw Error("์žฅ๋ฐ”๊ตฌ๋‹ˆ ์ •๋ณด๊ฐ€ ์—†๋‹ค!");

      await updateDoc(cartRef, {
        amount,
      });

      const cartSnapshot = await getDoc(cartRef);

      return {
        ...cartSnapshot.data(),
        id: cartSnapshot.id,
      };
    },

 

3) deleteCart

  • deleteDoc์— ์ปฌ๋ž™์…˜๊ณผ ํ•ด๋‹น ๋ฐ์ดํ„ฐ ID๋ฅผ ๋„ฃ๊ธฐ
   deleteCart: async (parent, { cartId }) => {
      const cartRef = doc(db, "cart", cartId);
      if (!cartRef) throw new Error("์žฅ๋ฐ”๊ตฌ๋‹ˆ ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค");
      await deleteDoc(cartRef);
      return cartId;
    },

 

4) executePay

executePay: async (parent, { ids }) => {
      // createdAt์ด ๋น„์–ด์žˆ์ง€ ์•Š์€ ids๋“ค์— ๋Œ€ํ•ด์„œ ๊ฒฐ์ œ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ 
      // cart์—์„œ ์ด๋“ค์˜ ids๋ฅผ ์ง€์›Œ์ค€๋‹ค.

      const deleted = [];

      for await (const id of ids) {
        const cartRef = doc(db, "cart", id);
        const cartSnapshot = await getDoc(cartRef);
        const cartData = cartSnapshot.data();
        const productRef = cartData?.product;
        if (!productRef) throw Error("์ƒํ’ˆ์ •๋ณด๊ฐ€ ์—†๋‹ค.");

        const product = (await getDoc(productRef)).data() as Product;

        if (product.createdAt) {
          await deleteDoc(cartRef);
          deleted.push(id);
        }
      }

      return deleted;
    },

 

 

๋!! ๋‹ค์Œ์€ ๋ฐฐํฌ๋‹ค,, ๊ทผ๋ฐ ๊ฐ•์˜์—์„œ ๋‹ค๋ฃฌ heroku๋Š” ๋ฌด๋ฃŒ๊ฐ€ ๋๋‚ฌ๋‹ค๋˜๋ฐ.. ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ด์•ผํ•  ๊ฒƒ ๊ฐ™๋‹ค

๋‘๊ทผ๋‘๊ทผ..