[ React ] json DB 생성 & resolver 연동 & 클라이언트에 DB 반영

CJY00N·2023년 7월 15일
0

react

목록 보기
7/10
post-thumbnail

⚡️ json DB 생성

dbController 작성

  • DBfield와 basePath, filenames를 정의한다.
  • readDB와 writeDB를 정의한다.

▼ server/src/dbController.ts

export enum DBfield {
  CART = "cart",
  PRODUCTS = "products",
}

const basePath = resolve();

const filenames = {
  [DBfield.CART]: resolve(basePath, "src/db/cart.json"),
  [DBfield.PRODUCTS]: resolve(basePath, "src/db/products.json"),
};

export const readDB = (target: DBfield) => {
  try {
    return JSON.parse(fs.readFileSync(filenames[target], "utf-8"));
  } catch (err) {
    console.error(err);
  }
};

export const writeDB = (target: DBfield, data: any) => {
  try {
    fs.writeFileSync(filenames[target], JSON.stringify(data, null, " "));
  } catch (err) {
    console.error(err);
  }
};

json 파일 작성

  • db에서 불러 올 cart data와 products data를 각각 정의한다.
    ▼ src/db/products.json , server/src/db/cart.json

ApolloServer 재정의

  • db를 정의했으므로, 불러와서 서버의 context에 추가한다.

▼ server/src/index.ts

(async () => {
  const server = new ApolloServer({
    typeDefs: schema,
    resolvers,
    context: {
      db: {
        products: readDB(DBfield.PRODUCTS),
        cart: readDB(DBfield.CART),
      },
    },
  });

⚡️ Resolver와 DB연동

타입 정의하기

  • client에서 정의한 타입을 그대로 사용한다.

▼ server/src/resolvers/type.ts

export type Product = {
  id: string;
  imageUrl: string;
  price: number;
  title: string;
  description: string;
  createdAt: number;
};

export type Products = Product[];

export type CartItem = {
  id: string;
  amount: number;
};

export type Cart = CartItem[];
  • Resolver type의 context.db의 type을 Cart와 Products 타입으로 정의한다.
export type Resolver = {
  [k: string]: {
    [key: string]: (
      parent: any,
      args: { [key: string]: any },
      context: {
        db: { cart: Cart; products: Products; };
      },
      info: any
    ) => any;
  };
};

product resolver 연동

  • context에 db가 들어간다.

▼ server/src/resolvers/product.ts

const productResolver: Resolver = {
  Query: {
    products: (parent, args, { db }) => {
      return db.products;
    },
    product: (parent, { id }, { db }) => {
      const found = db.products.find((item) => item.id === id);
      if (found) return found;
      return null;
    },
  },
};

cart resolver 연동

  • cart는 mutation을 통해 db가 바뀌면 write도 해주어야 하므로, setJSON도 정의해야 한다.

▼ server/src/resolvers/cart.ts

const setJSON = (data: Cart) => writeDB(DBfield.CART, data);
  • 기존의 쿼리와 mutation에서 조금만 수정하면 된다.
const cartResolver: Resolver = {
  Query: {
    cart: (parent, args, { db }) => {
      return db.cart;
    },
  },
  Mutation: {
    addCart: (parent, { id }, { db }) => {
      if (!id) throw Error("상품 id가 없습니다.");
      const targetProduct = db.products.find((item) => item.id === id);

      if (!targetProduct) {
        throw new Error("상품이 없습니다.");
      }

      const existCartItemIndex = db.cart.findIndex((item) => item.id === id);
      if (existCartItemIndex > -1) {
        // 카트에 이미 있으면
        const newCartItem = {
          id,
          amount: db.cart[existCartItemIndex].amount + 1,
        };
        db.cart.splice(existCartItemIndex, 1, newCartItem);
        setJSON(db.cart);
        return newCartItem;
      }

      const newItem = {
        id,
        amount: 1,
      };
      db.cart.push(newItem);
      setJSON(db.cart);
      return newItem;
    },
    updateCart: (parent, { id, amount }, { db }) => {
      const existCartItemIndex = db.cart.findIndex((item) => item.id === id);
      if (existCartItemIndex < 0) {
        throw new Error("없는 데이터입니다.");
      }

      const newCartItem = {
        id,
        amount: amount,
      };

      db.cart.splice(existCartItemIndex, 1, newCartItem);
      setJSON(db.cart);
      return newCartItem;
    },
    deleteCart: (parent, { id }, { db }) => {
      const existCartItemIndex = db.cart.findIndex((item) => item.id === id);
      if (existCartItemIndex < 0) {
        throw new Error("없는 데이터입니다.");
      }

      db.cart.splice(existCartItemIndex, 1);
      setJSON(db.cart);
      return id;
    },
    executePay: (parent, { ids }, { db }) => {
      const newCartData = db.cart.filter(
        (cartItem) => !ids.includes(cartItem.id)
      );
      db.cart = newCartData;
      setJSON(db.cart);
      return ids;
    },
  },
  CartItem: {
    product: (cartItem, args, { db }) => {
      return db.products.find((product) => product.id === cartItem.id);
    },
  },
};

⚡️ 서버의 변경사항 클라이언트에 반영하기

mock 사용해제

이제 mock을 사용하지 않을 것이기 때문에 client에서 사용한 mock과 관련된 것들을 모두 지워준다.

  • yarn remove msw : msw 삭제
  • mocks 디렉토리에 있던 browser.ts, handler.ts를 삭제한다.
  • _layout.tsx에서 worker를 불러오고 start하는 부분도 삭제한다.

클라이언트에서 데이터 불러오기

  • BASE_URL을 변경해준다.
    ▼ client/src/queryClient.ts
    const BASE_URL = "http://localhost:8000/graphql";
  • client의 GET_PRODUCT와 GET_PRDUCTS 쿼리를 server에서 정의한 쿼리에 맞게 수정한다. (grqphql는 products, product처럼 하나로 감싸져있어야 하는 규칙이 있다.)
  • cart의 GET_CART, ADD_CART, UPDATE_CART, DELTE_CART쿼리를 server에서 정의한 쿼리에 맞게 수정한다.
  • payment의 EXECUTE_PAY 쿼리도 server에서 정의한 쿼리에 맞게 수정한다.

이후 쿼리를 호출하는 코드, 함수들을 위의 규칙에 맞게 수정하면 서버의 변경사항이 클라이언트에 동일하게 적용된다.

db 변경될 때마다 서버 재시작되는 문제

[nodemon] restarting due to changes...

  • updateCart, addCart 등 db의 내용이 바뀌면 서버가 재시작되는 문제가 발생했다.

  • 불필요하게 서버를 계속 재시작하는 것은 과부하가 올 수 있고,
    서버를 재시작하는 도중에 mutataion을 하면

    TypeError: Network request failed at xhr.onerror (browser-ponyfill.js:480:16)
    POST http://localhost:8000/graphql net::ERR_CONNECTION_REFUSED

  • 위와 같은 오류가 콘솔에서 발생하면서 반영되지 않는 문제도 있었다.

  • nodemon은 소스코드 변경 시에 자동으로 재시작해주는 기능이 있기 때문에 발생하는 문제다.

해결책은 db 변경 감지를 무시하도록 하는 것이다.

▼ nodemon.json

{
  "ignore": ["src/db/*"]
}

db 변경은 src/db에 있는 json 파일들이 변경되므로 위와 같이 설정해주었다.
필요에 따라 경로를 지정하고, 추가하면 에러를 해결할 수 있을 것이다.

profile
COMPUTER SCIENCE ENGINEERING / Web Front End

0개의 댓글