벌써 절반왔다 절반!! 끝까지 파이팅 💪🏻
💡 오늘 할 것
1. 모노레포로 변경 / 서버 환경 설정
2. schema 정의
3. resolver 정의
4. 서버 실행하기
1️⃣ BFF형식의 서버 생성하기 / monorepo workspace로 변경
- BFF 서버란?
: 프론트엔드 요구사항에 맞게 구현하기 위해 도움을 주는 서버
- monorepo란?
- Workspaces는 단일 최상위 루트 패키지 내에서 로컬 파일 시스템의 여러 패키지를 관리할 수 있도록 지원하는 npm cli의 기능 집합을 가리키는 일반 용어
- Workspace를 통해 로컬 파일 시스템에서 연결된 패키지를 훨씬 더 효율적으로 처리함
- root/package.json
{
"name": "shopping-mall",
"version": "1.0.0",
"main": "index.js",
"repository": "https://github.com/yegri/react_shoppingMall_project.git",
"author": "Yeeun Lee <exsilver305@gmail.com>",
"license": "MIT",
"private": true,
"workspaces": [
"client",
"server"
],
"scripts": {
"client": "yarn workspace client run dev",
"server": "yarn workspace server run dev"
}
}
- 폴더 구조

2️⃣ Apollo Server 설치하기
- GraphQL을 편하게 사용할 수 있도록 도와주는 라이브러리
- Apollo Server는 GraphQL API를 제공하는 서버를 개발할 수 있게 도와주는 패키지로 Node.js에서 사용하는 Express와 비슷한 역할 을 한다.
1. 설치
- 서버로 이동 후
yarn add express apollo-server apollo-server-express graphql
yarn add --dev ts-node @types/node
yarn add typescript
2. server/tsconfig.json
{
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": false,
"outDir": "dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
3. server/package.json
"scripts": {
"dev": "nodemon --exec 'ts-node src/index.ts'"
}
- 서버 실행할 때 계속 NOT FOUND MODULE .. .src/index.ts 이러면서 에러가 났었는데..
- 계속 구글링하고 강의 봐도 뭐가 문제인지 모르겠는 거였다,,, 진짜 너무너무 포기하고 싶었는데 각잡고 앉아서 chat GPT한테도 물어보고 계속 찾아보고 수정해보고.. 하다가 원인을 발견했다!
- .src/index.ts 이 경로가 안 맞아서 그런 거였다..! src/index.ts로 바꾸니까 해결.. 😭 나에게 왜 자꾸 이런 시련을 주시나요 ㅠㅠ
4. server/nodemon.json
{
"watch": ["src"],
"ignore": ["db/**/*"],
"env": {
"NODE_ENV": "development"
}
}
5. server/src/index.ts
import express from "express";
import { ApolloServer } from "apollo-server-express";
import schema from "./schema";
import resolvers from "./resolvers";
(async () => {
const server = new ApolloServer({
typeDefs: schema,
resolvers,
});
const app = express();
await server.start();
server.applyMiddleware({
app,
path: "/graphql",
cors: {
origin: ["http://localhost:5173", "https://studio.apollographql.com"],
credentials: true,
},
});
await app.listen({ port: 8000 });
console.log("server listening on 8000...");
})();
- typeDefs와 resolvers를 ApolloServer 생성자에 넘겨 GraphQL 서버 인스턴스를 생성하고 그 서버를 시작해주는 코드를 작성
3️⃣ Schema 정의
💻 스키마는 서버에서 어떻게 데이터를 요청할지 정의한 파일
- 요청 시 어떤 데이터를 얼마나 요청할지, 각각의 데이터의 자료형이 무엇이고, 어떤 데이터를 필수로 요청할지에 대한 정보가 담김
- 즉, 사용자는 반드시 스키마에 정의된 형태로 서버에 요청해야 함
💻 스키마에는 Query, Mutation과 같이 2가지 요청이 존재
- Query : 데이터베이스에서 데이터를 읽는 요청
- Mutation : 데이터베이스를 수정하는 요청
이렇게 구분을 하는 이유는 데이터베이스 읽기 요청은 무한정으로 동시에 수행 가능하지만, 데이터베이스 수정 요청은 순차적으로 수행되어야하기 때문!!
1. server/src/schema/product.ts
import { gql } from "apollo-server-express";
const productSchema = gql`
type Product {
id: String!
imageUrl: String!
price: Int!
title: String!
description: String
createdAt: Float
}
extend type Query {
products: [Product!]
product(id: ID!): Product!
}
`;
export default productSchema;
2. server/src/schema/cart.ts
import { gql } from "apollo-server-express";
const cartSchema = gql`
type CartItem {
id: ID!
imageUrl: String!
price: Int!
title: String!
amount: Int!
}
extend type Query {
cart: [CartItem!]
}
extend type Mutation {
addCart(id: ID!): CartItem!
updateCart(id: ID!, amount: Int!): CartItem!
deleteCart(id: ID!): ID!
executePay(ids: [ID!]): [ID!]
}
`;
export default cartSchema;
3. server/src/schema/index.ts
import { gql } from "apollo-server-express";
import productSchema from "./product";
import cartSchema from "./cart";
const linkSchema = gql`
type Query {
_: Boolean
}
type Mutation {
_: Boolean
}
`;
export default [linkSchema, productSchema, cartSchema];
4️⃣ Resolver 정의
💻 Resolver는 사용자가 쿼리를 요청했을 때 이를 서버가 어떻게 처리할지 정의하는 파일
- resolver는 요청에 대해 단순히 데이터를 반환할 수도 있고, 직접 데이터를 찾거나, 메모리에 접근하거나, 다른 API에 요청해서 데이터를 가져올 수 있다
1. src/resolvers/product.ts
import { Resolver } from "./types";
// 상품 mock data
const mock_products = (() =>
Array.from({ length: 20 }).map((_, i) => ({
id: i + 1 + "",
imageUrl: `https://source.unsplash.com/200x150/?nature/${i + 1}`,
price: 50000,
title: `임시상품${i + 1}`,
description: `임시상세내용${i + 1}`,
createdAt: new Date(1645735501883 + i * 1000 * 60 * 60 * 10).toString(),
})))();
const productResolver: Resolver = {
Query: {
products: (parent, args, context, info) => {
return mock_products;
},
product: (parent, { id }, context, info) => {
const found = mock_products.find((item) => item.id === id);
if (found) return found;
return null;
},
},
};
export default productResolver;
2. src/resolvers/cart.ts
import { Resolver } from "./types";
// 상품 mock data
const mock_products = (() =>
Array.from({ length: 20 }).map((_, i) => ({
id: i + 1 + "",
imageUrl: `https://source.unsplash.com/200x150/?nature/${i + 1}`,
price: 50000,
title: `임시상품${i + 1}`,
description: `임시상세내용${i + 1}`,
createdAt: new Date(1645735501883 + i * 1000 * 60 * 60 * 10).toString(),
})))();
let cartData = [
{ id: "1", amount: 1 },
{ id: "2", amount: 2 },
];
const cartResolver: Resolver = {
Query: {
cart: (parent, args, context, info) => {
return cartData;
},
},
Mutation: {
addCart: (parent, { id }, context, info) => {
const newCartData = { ...cartData };
const targetProduct = mock_products.find((item) => item.id === id);
if (!targetProduct) {
throw new Error("상품이 없습니다");
}
const newItem = {
...targetProduct,
amount: (newCartData[id]?.amount || 0) + 1,
};
newCartData[id] = newItem;
cartData = newCartData;
return newItem;
},
updateCart: (parent, { id, amount }, context, info) => {
const newData = { ...cartData };
if (!newData[id]) {
throw new Error("없는 데이터입니다");
}
const newItem = {
...newData[id],
amount,
};
newData[id] = newItem;
cartData = newData;
return newItem;
},
deleteCart: (parent, { id }, context, info) => {
const newData = { ...cartData };
delete newData[id];
cartData = newData;
return id;
},
executePay: (parent, { ids }, context, info) => {
const newCartData = cartData.filter(
(cartItem) => !ids.includes(cartItem.id)
);
cartData = newCartData;
return ids;
},
},
};
export default cartResolver;
3. src/resolvers/index.ts
import productResolver from "./product";
import cartResolver from "./cart";
export default [productResolver, cartResolver];
5️⃣ 서버 실행
yarn run server

- 이 페이지가 보이면 성공!
💻 쿼리 작성해서 데이터 잘 불러오는지 확인


5일차 끝!!!
서버 생성하고 불러오고 쿼리 작성하고... 이런 건 처음 해보는데 오류 때문에 힘들었지만.. ㅎㅎㅎ
그래도 신기하고 재미있었다!
이렇게 한발짝 한발짝 나아가면 되는 거지~~
'REACT > 쇼핑몰 프로젝트' 카테고리의 다른 글
| [REACT] React + Typescript + Vite + SCSS로 만드는 쇼핑몰 개인프로젝트 (7/10) (0) | 2024.01.24 |
|---|---|
| [REACT] React + Typescript + Vite + SCSS로 만드는 쇼핑몰 개인프로젝트 (6/10) (0) | 2024.01.23 |
| [REACT] React + Typescript + Vite + SCSS로 만드는 쇼핑몰 개인프로젝트 (4/10) (0) | 2024.01.22 |
| [REACT] React + Typescript + Vite + SCSS로 만드는 쇼핑몰 개인프로젝트 (3/10) (0) | 2024.01.18 |
| [REACT] React + Typescript + Vite + SCSS로 만드는 쇼핑몰 개인프로젝트 (1/10) (1) | 2024.01.13 |