Chapter 6. ORM 사용해보기
☑️ 실습 인증
🔨ORM 사용해보기 실습
다음 명령어로 prisma 라이브러리를 설치했습니다.
npm install @prisma/client prisma
👉 Prisma 설정 파일 만들기
- 다음과 같은 명령어를 사용해서 prisma 설정 파일을 만들어주었습니다.
npm exec prisma init

2. 또한 provider 를 postgresql에서 mysql로 변경해줬습니다. (prisma는 postgresql이 디폴트)

👉 Prisma Schema에 Model 추가하기
과제로 진행했던 데이터베이스의 users를 prisma schema로 바꿨습니다.
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model users {
user_id Int @id @default(autoincrement())
user_name String @unique @db.VarChar(50)
password String @db.VarChar(255)
email String @unique @db.VarChar(100)
created_at DateTime @default(now()) @db.Timestamp(6)
gender String @db.VarChar(15)
birth DateTime? @db.Date
address String @db.VarChar(255)
detailAddress String? @map("detail_address") @db.VarChar(255)
phoneNumber String @map("phone_number") @db.VarChar(20)
}
👉 Client 코드 만들기
1. 다음과 같은 명령어를 실행하여 generate를 하였습니다.
npm exec prisma generate

또한 package.json에 dev 코드도 추가해줬습니다.
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node src/index.js",
"dev": "nodemon -e js,json,prisma --exec \"prisma generate && node src/index.js\""
},
👉 ORM 적용하기
- users 뿐만 아니라 전체 모델 코드를 적용해주었습니다
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
// Enum 정의
enum MissionStatus {
IN_PROGRESS
COMPLETED
}
enum UserMissionStatus {
IN_PROGRESS
COMPLETED
}
// 지역 모델
model regions {
id Int @id @default(autoincrement())
region_name String @unique @db.VarChar(100)
stores stores[]
missions missions[]
@@map("regions")
}
// 사용자 모델
model users {
id Int @id @default(autoincrement())
user_name String @unique @db.VarChar(50)
password String @db.VarChar(255)
email String @unique @db.VarChar(100)
created_at DateTime @default(now()) @db.Timestamp(6)
gender String @db.VarChar(15)
birth DateTime? @db.Date
address String @db.VarChar(255)
detailAddress String? @map("detail_address") @db.VarChar(255)
phoneNumber String @map("phone_number") @db.VarChar(20)
reviews reviews[]
user_points user_points[]
user_missions user_missions[]
user_favor_category user_favor_category[]
@@map("users")
}
// 리뷰 모델
model reviews {
id Int @id @default(autoincrement())
user users @relation(fields: [userId], references: [id])
userId Int @map("user_id")
store stores @relation(fields: [storeId], references: [id])
storeId Int @map("store_id")
rating Int @db.Int
review_text String? @db.Text
created_at DateTime @default(now()) @db.Timestamp(6)
@@map("reviews")
}
// 미션 모델
model missions {
id Int @id @default(autoincrement())
region regions @relation(fields: [regionId], references: [id])
regionId Int @map("region_id")
mission_status MissionStatus
description String @db.Text
created_at DateTime @default(now()) @db.Timestamp(6)
user_missions user_missions[]
@@map("missions")
}
// 가게 모델
model stores {
id Int @id @default(autoincrement())
store_name String @db.VarChar(100)
region regions? @relation(fields: [regionId], references: [id])
regionId Int? @map("region_id")
store_address String? @db.VarChar(255)
reviews reviews[]
user_missions user_missions[]
@@map("stores")
}
// 사용자 포인트 모델
model user_points {
id Int @id @default(autoincrement())
user users @relation(fields: [userId], references: [id])
userId Int @map("user_id")
points Int @default(0)
updated_at DateTime @default(now()) @db.Timestamp(6)
user_missions user_missions[]
@@map("user_points")
}
// 사용자 미션 모델
model user_missions {
id Int @id @default(autoincrement())
user users @relation(fields: [userId], references: [id])
userId Int @map("user_id")
mission missions @relation(fields: [missionId], references: [id])
missionId Int @map("mission_id")
store stores @relation(fields: [storeId], references: [id])
storeId Int @map("store_id")
point user_points @relation(fields: [pointId], references: [id])
pointId Int @map("point_id")
status UserMissionStatus
completed_at DateTime?
@@map("user_missions")
}
// 음식 카테고리 모델
model food_category {
id Int @id @default(autoincrement())
name String @unique @db.VarChar(100)
user_favor_category user_favor_category[]
@@map("food_category")
}
// 사용자 선호 카테고리 모델
model user_favor_category {
id Int @id @default(autoincrement())
user users @relation(fields: [userId], references: [id])
userId Int @map("user_id")
foodCategory food_category @relation(fields: [foodCategoryId], references: [id])
foodCategoryId Int @map("food_category_id")
@@index([foodCategoryId], map: "f_category_id")
@@index([userId], map: "user_id_idx")
@@map("user_favor_category")
}
2. src/repositories/user.repository.js , src/dtos/user.dto.js 수정
💠src/repositories/user.repository.js
import { pool } from "../db.config.js";
import { prisma } from "../db.config.js";
// User 데이터 삽입
export const addUser = async (data) => {
const user = await prisma.users.findFirst({ where: { email: data.email } });
if (user) {
return null;
}
const created = await prisma.users.create({ data: data });
return created.id;
};
// 사용자 정보 얻기
export const getUser = async (userId) => {
const user = await prisma.users.findFirstOrThrow({ where: { id: userId } });
return user;
};
// 음식 선호 카테고리 매핑
export const setPreference = async (userId, foodCategoryId) => {
await prisma.user_favor_category.create({
data: {
userId: userId,
foodCategoryId: foodCategoryId,
},
});
};
// 사용자 선호 카테고리 반환
export const getUserPreferencesByUserId = async (userId) => {
const preferences = await prisma.user_favor_category.findMany({
select: {
id: true,
userId: true,
foodCategoryId: true,
foodCategory: {
select: {
id: true,
name: true,
},
},
},
where: { userId: userId },
orderBy: { foodCategoryId: "asc" },
});
return preferences;
};
💠 src/dtos/user.dto.js
export const bodyToUser = (body) => {
const birth = new Date(body.birth);
return {
email: body.email,
name: body.name,
gender: body.gender,
birth:birth.gender,
address: body.address || "",
detailAddress: body.detailAddress || "",
phoneNumber: body.phoneNumber,
preferences: body.preferences,
};
};
export const responseFromUser = ({ user, preferences }) => {
const preferFoods = preferences.map(
(preference) => preference.foodCategory.name
);
return {
email: user.email,
name: user.name,
preferCategory: preferFoods,
};
};
npm exec prisma migrate dev 명령어 실행하여 테이블 생성
👉 마무리
생성된 테이블을 확인하기 위해서 다음 명령어 실행
npx prisma studio

🍍 DB 마이그레이션 파일 관리해보기
👉 npm exec prisma migrate dev
다음 명령어 사용하여 마이그레이션 하였습니다.

👉npx prisma studio 명령어로 테이블 생성확인

📜 목록 API 만들어보기
1. DTO 파일: review.dto.js
단일 리뷰를 변환하는 responseFromReview와 리뷰 목록을 변환하는 responseFromReviews 함수를 구현했습니다.
// src/dtos/review.dto.js
export const bodyToReview = (body) => ({
userId: body.userId,
storeId: body.storeId,
rating: body.rating,
reviewText: body.reviewText,
});
export const responseFromReview = (review) => ({
reviewId: review.id,
userId: review.user?.id,
storeId: review.store?.id,
rating: review.rating,
reviewText: review.review_text,
createdAt: review.created_at,
user: review.user
? {
userName: review.user.user_name,
email: review.user.email,
gender: review.user.gender,
birth: review.user.birth,
address: review.user.address,
detailAddress: review.user.detailAddress,
phoneNumber: review.user.phoneNumber,
}
: null,
store: review.store
? {
storeName: review.store.store_name,
}
: null,
}); // 단일 리뷰 변환
export const responseFromReviews = (reviews) => {
if (!Array.isArray(reviews) || reviews.length === 0) {
return {
data: [],
pagination: { cursor: null },
};
}
return {
data: reviews.map((review) => responseFromReview(review)),
pagination: {
cursor: reviews.length ? reviews[reviews.length - 1].id : null,
},
};
};// 전체 리뷰 목록 변환
2. 레포지토리 파일: user.repository.js
getAllStoreReviews로 특정 가게의 리뷰 목록을 데이터베이스에서 가져오는 역할을 합니다.
export const getAllStoreReviews = async (storeId, cursor) => {
const reviews = await prisma.reviews.findMany({
select: {
id: true,
review_text: true, // 리뷰 내용
rating: true, // 리뷰 평점
created_at: true, // 작성일
store: {
select: { id: true, store_name: true },
},
user: {
select: {
id: true,
user_name: true,
email: true,
gender: true,
birth: true,
address: true,
detailAddress: true,
phoneNumber: true,
},
},
},
where: { storeId: storeId, id: { gt: cursor } },
orderBy: { id: "asc" },
take: 5,
});
console.log("Reviews found:", reviews); // 쿼리 결과 로그
return reviews;
};
3. 서비스 파일: store.service.js
데이터베이스에서 getAllStoreReviews로 리뷰 목록을 조회하고, DTO를 통해 응답 형식으로 변환한 후 반환했습니다.
// src/services/store.service.js
import { addStore } from "../repositories/store.repository.js";
import { responseFromStore } from "../dtos/store.dto.js";
import { getAllStoreReviews } from '../repositories/user.repository.js';
import {responseFromReviews} from '../dtos/review.dto.js'
export const storeSignUp = async (data) => {
const storeId = await addStore({
storeName: data.storeName,
address: data.address,
regionId: data.regionId,
});
if (!storeId) {
throw new Error("가게를 추가할 수 없습니다.");
}
return responseFromStore({ storeId, ...data });
};
export const listStoreReviews = async (storeId, cursor) => {
const reviews = await getAllStoreReviews(storeId, cursor);
console.log("Raw reviews from DB:", reviews); // 리뷰 배열이 제대로 반환되는지 확인
return responseFromReviews(reviews); // 리뷰 배열을 변환하여 반환
};
4. Controller 파일: store.controller.js
컨트롤러에서 listStoreReviews 함수를 호출하여 최종 데이터를 반환하게 코드를 짰습니다.
(클라이언트로는 JSON 형식으로 응답)
// src/controller/store.controller.js
import { StatusCodes } from "http-status-codes";
import { bodyToStore } from "../dtos/store.dto.js";
import { storeSignUp } from "../services/store.service.js";
import { listStoreReviews } from "../services/store.service.js";
export const handleStoreSignUp = async (req, res, next) => {
try {
const store = await storeSignUp(bodyToStore(req.body));
res.status(StatusCodes.CREATED).json({ result: store });
} catch (error) {
res.status(StatusCodes.BAD_REQUEST).json({ error: error.message });
}
};
export const handleListStoreReviews = async (req, res, next) => {
try {
const storeId = parseInt(req.params.storeId, 10);
const cursor = req.query.cursor ? parseInt(req.query.cursor, 10) : 0;
const reviews = await listStoreReviews(storeId, cursor);
res.status(StatusCodes.OK).json(reviews); // 데이터를 JSON으로 반환
} catch (error) {
console.error(error);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: '리뷰 조회에 실패했습니다.' });
}
};
최종 index.js 파일
// 작성한 리뷰 목록 확인
app.get("/api/stores/:storeId/reviews", handleListStoreReviews)
☑️실행결과
### 특정 가게의 리뷰 목록 조회 - 첫 페이지
GET <http://localhost:3000/api/stores/1/reviews?cursor>
Content-Type: application/json

🎯핵심 키워드 정리
💠ORM
🔹ORM 이란?
Object-Relational Mapping
- 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 것을 말한다
- 객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터베이스는 테이블을 사용한다
- 객체 모델과 관계형 모델 간에 불일치가 존재한다
- ORM을 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결해준다
💠 Prisma 문서 살펴보기
🔹 Prisma의 Connection Pool 관리 방법
Connection Pool을 관리하는 방법은 기본적으로 데이터베이스 클라이언트 라이브러리를 통해 이루어진다.
Prisma는 데이터베이스 연결을 관리할 때, 기본적으로 각 요청마다 새 연결을 생성하기보다 데이터베이스와의 지속적인 연결을 유지하고 재사용하여 성능을 최적화한다.
Prisma에서 Connection Pool을 관리하기 위해서는:
- DATABASE_URL에서 connection_limit 등의 매개변수를 지정
- 서버리스 환경에서는 전역 변수로 Prisma Client를 유지해 연결 재사용
- 추가적인 세부 관리가 필요할 경우, 서드파티 라이브러리와 Prisma를 조합
🔹 Prisma의 Migration 관리 방법
- 마이그레이션 설정 준비
- 새로운 마이그레이션 생성
npx prisma migrate dev --name <이름>
3. 마이그레이션 적용
npx prisma migrate deploy
💠 ORM(Prisma)을 사용하여 좋은 점과 나쁜 점
🔹 ORM 장점
- 객체 지향적인 코드로 인해 더 직관적이고, 비즈니스 로직에 더 집중할 수 있게 도와준다.
- 재사용성 및 유지보수의 편리성이 증가
- DBMS에 대한 종속성이 줄어든다
🔹 ORM 단점
- 완벽한 ORM으로만 서비스를 구현하기가 어렵다.
- 프로젝트의 복잡성이 커질 경우 난이도 또한 올라갈 수 있다.
- 잘못 구현된 경우에 속도 저하 및 심각할 경우 일관성이 무너지는 문제점이 생길 수 있다.
- 프로시저가 많은 시스템에서는 ORM의 객체 지향적인 장점을 활용하기 어렵다.
- 이미 프로시저가 많은 시스템에서는 다시 객체로 바꿔야함, 그 과정에서 생산성 저하나 리스크가 많이 발생할 수 있다.
💠 다양한 ORM 라이브러리 살펴보기
🔹 Sequelize
- Node.js 환경에서 MySQL, PostgreSQL, SQLite, MSSQL 등 여러 관계형 데이터베이스를 쉽게 다룰 수 있도록 지원하는 ORM(Object-Relational Mapping) 라이브러리
🔹 TypeORM
- TypeORM은 데코레이터 기반의 문법을 사용해 객체 모델을 정의하고 이를 데이터베이스 테이블로 매핑하여 관리한다.
✏️6주차 필기 및 마인드맵



🔥 미션 인증
미션 기록🍀
기본적으로 cursor 페이지네이션을 사용하여 구현하였습니다.
또한 미션을 진행하면서 db를 보완, 수정하는 과정도 함께 기록하였습니다.
Prisma ORM 초기 셋팅
저는 다음과 같이 model을 정의하였습니다.⬇️
// This is your Prisma schema file,
// learn more about it in the docs: <https://pris.ly/d/prisma-schema>
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: <https://pris.ly/cli/accelerate-init>
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
// Enum 정의
enum MissionStatus {
IN_PROGRESS
COMPLETED
}
enum UserMissionStatus {
IN_PROGRESS
COMPLETED
}
// 지역 모델
model regions {
id Int @id @default(autoincrement())
region_name String @unique @db.VarChar(100)
stores stores[]
missions missions[]
@@map("regions")
}
// 사용자 모델
model users {
id Int @id @default(autoincrement())
user_name String @unique @db.VarChar(50)
password String @db.VarChar(255)
email String @unique @db.VarChar(100)
created_at DateTime @default(now()) @db.Timestamp(6)
gender String @db.VarChar(15)
birth DateTime? @db.Date
address String @db.VarChar(255)
detailAddress String? @map("detail_address") @db.VarChar(255)
phoneNumber String @map("phone_number") @db.VarChar(20)
reviews reviews[]
user_points user_points[]
user_missions user_missions[]
user_favor_category user_favor_category[]
@@map("users")
}
// 리뷰 모델
model reviews {
id Int @id @default(autoincrement())
user users @relation(fields: [userId], references: [id])
userId Int @map("user_id")
store stores @relation(fields: [storeId], references: [id])
storeId Int @map("store_id")
rating Int @db.Int
review_text String? @db.Text
created_at DateTime @default(now()) @db.Timestamp(6)
@@map("reviews")
}
// 미션 모델
model missions {
id Int @id @default(autoincrement())
region regions @relation(fields: [regionId], references: [id])
regionId Int @map("region_id")
mission_status MissionStatus
description String @db.Text
created_at DateTime @default(now()) @db.Timestamp(6)
user_missions user_missions[]
@@map("missions")
}
// 가게 모델
model stores {
id Int @id @default(autoincrement())
store_name String @db.VarChar(100)
region regions? @relation(fields: [regionId], references: [id])
regionId Int? @map("region_id")
store_address String? @db.VarChar(255)
reviews reviews[]
user_missions user_missions[]
@@map("stores")
}
// 사용자 포인트 모델
model user_points {
id Int @id @default(autoincrement())
user users @relation(fields: [userId], references: [id])
userId Int @map("user_id")
points Int @default(0)
updated_at DateTime @default(now()) @db.Timestamp(6)
user_missions user_missions[]
@@map("user_points")
}
// 사용자 미션 모델
model user_missions {
id Int @id @default(autoincrement())
user users @relation(fields: [userId], references: [id])
userId Int @map("user_id")
mission missions @relation(fields: [missionId], references: [id])
missionId Int @map("mission_id")
store stores @relation(fields: [storeId], references: [id])
storeId Int @map("store_id")
point user_points @relation(fields: [pointId], references: [id])
pointId Int @map("point_id")
status UserMissionStatus
completed_at DateTime?
@@map("user_missions")
}
// 음식 카테고리 모델
model food_category {
id Int @id @default(autoincrement())
name String @unique @db.VarChar(100)
user_favor_category user_favor_category[]
@@map("food_category")
}
// 사용자 선호 카테고리 모델
model user_favor_category {
id Int @id @default(autoincrement())
user users @relation(fields: [userId], references: [id])
userId Int @map("user_id")
foodCategory food_category @relation(fields: [foodCategoryId], references: [id])
foodCategoryId Int @map("food_category_id")
@@index([foodCategoryId], map: "f_category_id")
@@index([userId], map: "user_id_idx")
@@map("user_favor_category")
}
기존에 구현했던 API의 Repository 함수를 ORM을 사용하여 변경
challenge.repository 수정 전
import pool from "../db.config.js";
export const addChallenge = async (data) => {
const existing = await pool.query(
`SELECT * FROM user_missions WHERE user_id = ? AND mission_id = ? AND status = '진행 중'`,
[data.userId, data.missionId]
);
if (existing.length > 0) {
throw new Error("이미 도전 중인 미션입니다.");
}
const result = await pool.query(
`INSERT INTO user_missions (user_id, mission_id, store_id, status) VALUES (?, ?, ?, ?)`,
[data.userId, data.missionId, data.storeId, data.status]
);
return result.insertId;
};
☑️challenge.repository 수정 후
import { prisma } from "../db.config.js";
export const addChallenge = async (data) => {
const existing = await prisma.user_missions.findFirst({
where: {
user_id: data.userId,
mission_id: data.missionId,
status: '진행 중',
},
});
if (existing) {
throw new Error("이미 도전 중인 미션입니다.");
}
const result = await prisma.user_missions.create({
data: {
user_id: data.userId,
mission_id: data.missionId,
store_id: data.storeId,
status: data.status,
},
});
return result.id;
};
mission.repository.js 수정 전
import pool from "../db.config.js";
export const addMission = async (data) => {
const result = await pool.query(
`INSERT INTO missions (region_id, description, mission_status) VALUES (?, ?, ?)`,
[data.regionId, data.description, data.missionStatus]
);
return result.insertId;
};
☑️mission.repository.js 수정 후
import { prisma } from "../db.config.js";
export const addMission = async (data) => {
const result = await prisma.missions.create({
data: {
region_id: data.regionId,
description: data.description,
mission_status: data.missionStatus,
},
});
return result.id;
};
review.repository.js 수정 전
import pool from "../db.config.js";
export const addReview = async (data) => {
const result = await pool.query(
`INSERT INTO reviews (user_id, store_id, rating, review_text) VALUES (?, ?, ?, ?)`,
[data.userId, data.storeId, data.rating, data.reviewText]
);
return result.insertId;
};
export const checkStoreExists = async (storeId) => {
const store = await pool.query(`SELECT * FROM stores WHERE id = ?`, [storeId]);
return store.length > 0;
};
☑️review.repository.js 수정 후
import { prisma } from "../db.config.js";
export const addReview = async (data) => {
const result = await prisma.reviews.create({
data: {
user_id: data.userId,
store_id: data.storeId,
rating: data.rating,
review_text: data.reviewText,
},
});
return result.id;
};
export const checkStoreExists = async (storeId) => {
const store = await prisma.stores.findUnique({
where: { id: storeId },
});
return !!store;
};
store.repository.js 수정 전
import pool from "../db.config.js";
export const addStore = async (data) => {
const result = await pool.query(
`INSERT INTO stores (store_name, store_address, region_id) VALUES (?, ?, ?)`,
[data.storeName, data.address, data.regionId]
);
return result.insertId;
};
☑️store.repository.js 수정 후
import { prisma } from "../db.config.js";
export const addStore = async (data) => {
const result = await prisma.stores.create({
data: {
store_name: data.storeName,
store_address: data.address,
region_id: data.regionId,
},
});
return result.id;
};
user는 실습에서 따로 진행하였기 때문에 생략합니다!
DB 데이터 추가
API 요청을 확인하기 위해 더미데이터를 추가하였습니다.
-- 지역 데이터 생성
INSERT INTO regions (region_name) VALUES ('Seoul');
-- 가게 데이터 생성
INSERT INTO stores (store_name, region_id, store_address)
VALUES ('Test Store', 1, '123 Test Address');
-- 사용자 데이터 생성
INSERT INTO users (user_name, password, email, gender, birth, address, detail_address, phone_number)
VALUES
('testuser1', 'password123', 'testuser1@example.com', '남성', '1990-01-01', 'Seoul, South Korea', '101-202', '010-1234-5678'),
('testuser2', 'password123', 'testuser2@example.com', '여성', '1988-05-12', 'Busan, South Korea', '203-304', '010-5678-1234');
-- 리뷰 데이터 생성
INSERT INTO reviews (user_id, store_id, rating, review_text, created_at)
VALUES
(1, 1, 5, 'Great place!', NOW()),
(2, 1, 4, 'Nice ambiance but a bit pricey.', NOW());
-- 미션 데이터 생성
INSERT INTO missions (region_id, mission_status, description, created_at)
VALUES
(1, 'IN_PROGRESS', 'Promote local store', NOW()),
(1, 'COMPLETED', 'Complete a customer feedback survey', NOW());
-- 사용자 포인트 데이터 생성
INSERT INTO user_points (user_id, points, updated_at)
VALUES
(1, 100, NOW()),
(2, 150, NOW());
-- 사용자 미션 데이터 생성
INSERT INTO user_missions (user_id, mission_id, store_id, point_id, status, completed_at)
VALUES
(1, 1, 1, 1, 'IN_PROGRESS', NULL),
(2, 2, 1, 2, 'COMPLETED', NOW());
-- 음식 카테고리 데이터 생성
INSERT INTO food_category (name)
VALUES ('Vegetarian'), ('Fast Food'), ('Desserts');
-- 사용자 선호 카테고리 데이터 생성
INSERT INTO user_favor_category (user_id, food_category_id)
VALUES (1, 1), (2, 2);
내가 작성한 리뷰 목록
1. getUserReviews 함수 생성
- userId를 기반으로 사용자가 작성한 리뷰 목록을 조회하는 함수를 user.repository.js 파일에 추가했습니다.
export const getUserReviews = async (userId, cursor = 0) => {
const reviews = await prisma.reviews.findMany({
select: {
id: true,
review_text: true, // 리뷰 내용
rating: true, // 리뷰 평점
created_at: true, // 작성일
store: {
select: { id: true, store_name: true },
},
},
where: { userId: userId }, // userId 조건으로 조회
orderBy: { id: "asc" },
take: 5,
});
console.log("User Reviews found:", reviews); // 쿼리 결과 로그
return reviews;
};
2. review.dto.js
- 데이터베이스에서 가져온 리뷰 데이터를 클라이언트에 반환할 형식으로 변환하는 함수를 작성했습니다.
- responseFromReview는 단일 리뷰를, responseFromReviews는 리뷰 목록을 변환합니다.
export const responseFromReview = (review) => ({
reviewId: review.id,
userId: review.user?.id,
storeId: review.store?.id,
rating: review.rating,
reviewText: review.review_text,
createdAt: review.created_at,
store: review.store
? {
storeName: review.store.store_name,
}
: null,
});
export const responseFromReviews = (reviews) => ({
data: reviews.map((review) => responseFromReview(review)),
pagination: {
cursor: reviews.length ? reviews[reviews.length - 1].id : null,
},
});
3. user.service.js
- listUserReviews 함수는 getUserReviews 함수를 호출해 데이터를 가져오고, DTO 변환을 수행한 뒤 클라이언트에 반환할 형식으로 만들었습니다.
import { getUserReviews } from '../repositories/user.repository.js';
import { responseFromReviews } from '../dtos/review.dto.js';
export const listUserReviews = async (userId, cursor) => {
const reviews = await getUserReviews(userId, cursor);
return responseFromReviews(reviews); // 변환된 리뷰 데이터 반환
};
4. user.controller.js
- 클라이언트의 요청을 받아 listUserReviews 함수를 호출하는 코드를 작성해줬습니다.
export const handleListUserReviews = async (req, res) => {
try {
const userId = parseInt(req.params.userId, 10); // URL에서 userId 가져오기
const cursor = req.query.cursor ? parseInt(req.query.cursor, 10) : 0; // cursor 기본값 설정
const reviews = await listUserReviews(userId, cursor);
res.status(StatusCodes.OK).json(reviews);
} catch (error) {
console.error(error);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: '리뷰 조회에 실패했습니다.' });
}
};
5. index.js - 경로 설정 및 서버 실행
- 내가 작성한 리뷰 목록을 확인하는 api를 설정했습니다. (import는 생략)
// 내가 작성한 리뷰 목록 확인
app.get('/api/users/:userId/reviews', handleListUserReviews);
특정 가게의 미션 목록
☑️ db 수정
- missions 테이블에 storeId 컬럼 추가 ,-missions.store_id를 stores.id와 연결하는 외래 키 제약 조건 추가 → 이게 없어서 미션조회가 안되는 현상이 발생!
- missions 테이블에 storeId 컬럼 추가
ALTER TABLE missions
ADD COLUMN store_id INT;
- missions.store_id를 stores.id와 연결하는 외래 키 제약 조건 추가
ALTER TABLE missions
ADD CONSTRAINT fk_store_missions
FOREIGN KEY (store_id) REFERENCES stores(id)
ON DELETE SET NULL; -- 스토어 삭제 시 NULL로 설정 (필요에 따라 설정 가능)
☑️ 체크를 위한 더미 데이터 입력
-- 1. 지역 데이터 생성
INSERT INTO regions (region_name)
VALUES
('Seoul'),
('Busan');
-- 2. 가게 데이터 생성
INSERT INTO stores (store_name, region_id, store_address)
VALUES
('Test Store', 1, '123 Test Address, Seoul'),
('Seafood Delight', 2, '789 Ocean Road, Busan');
-- 3. 사용자 데이터 생성
INSERT INTO users (user_name, password, email, gender, birth, address, detail_address, phone_number)
VALUES
('testuser1', 'password123', 'testuser1@example.com', '남성', '1990-01-01', 'Seoul, South Korea', '101-202', '010-1234-5678'),
('testuser2', 'password123', 'testuser2@example.com', '여성', '1988-05-12', 'Busan, South Korea', '203-304', '010-5678-1234');
-- 4. 미션 데이터 생성
INSERT INTO missions (region_id, store_id, mission_status, description, created_at)
VALUES
(1, 1, 'IN_PROGRESS', 'Promote local store', NOW()),
(2, 2, 'COMPLETED', 'Organize a seafood tasting event', NOW());
-- 5. 리뷰 데이터 생성
INSERT INTO reviews (user_id, store_id, rating, review_text, created_at)
VALUES
(1, 1, 5, 'Great place!', NOW()),
(2, 1, 4, 'Nice ambiance but a bit pricey.', NOW());
-- 6. 사용자 포인트 데이터 생성
INSERT INTO user_points (user_id, points, updated_at)
VALUES
(1, 100, NOW()),
(2, 150, NOW());
-- 7. 사용자 미션 데이터 생성
INSERT INTO user_missions (user_id, mission_id, store_id, point_id, status, completed_at)
VALUES
(1, 1, 1, 1, 'IN_PROGRESS', NULL),
(2, 2, 2, 2, 'COMPLETED', NOW());
-- 8. 음식 카테고리 데이터 생성
INSERT INTO food_category (name)
VALUES
('Vegetarian'),
('Fast Food'),
('Desserts');
-- 9. 사용자 선호 카테고리 데이터 생성
INSERT INTO user_favor_category (user_id, food_category_id)
VALUES
(1, 1),
(2, 2);
1. mission.repository.js
- storeId를 기준으로 미션 목록을 조회하는 함수입니다
- . 페이지네이션을 위해 cursor를 사용해 특정 ID 이후의 데이터를 가져오도록 설정했습니다.
// src/repositories/mission.repository.js
import { prisma } from "../db.config.js";
export const getStoreMissions = async (storeId, cursor = 0) => {
const missions = await prisma.missions.findMany({
where: { storeId: storeId }, // storeId로 필터링
select: {
id: true,
description: true,
mission_status: true,
created_at: true,
region: {
select: { id: true, region_name: true },
},
},
orderBy: { id: "asc" },
take: 10,
skip: cursor,
});
return missions;
};
2. mission.dto.js
- 데이터베이스에서 가져온 미션 데이터를 클라이언트에 반환할 형식으로 변환하는 함수를 적어줬습니다.
// src/dtos/mission.dto.js
export const responseFromMission = (mission) => ({
missionId: mission.id,
description: mission.description,
status: mission.mission_status,
createdAt: mission.created_at,
region: mission.region ? mission.region.region_name : null,
});
export const responseFromMissions = (missions) => ({
data: missions.map((mission) => responseFromMission(mission)),
pagination: {
cursor: missions.length ? missions[missions.length - 1].id : null,
},
});
3. mission.service.js
- getStoreMissions 함수를 호출하여 미션 목록을 가져온 후, DTO로 변환하여 반환하는 서비스 함수를 구현했습니다.
// src/services/mission.service.js
import { getStoreMissions } from '../repositories/mission.repository.js';
import { responseFromMissions } from '../dtos/mission.dto.js';
export const listStoreMissions = async (storeId, cursor) => {
const missions = await getStoreMissions(storeId, cursor);
return responseFromMissions(missions); // 변환된 미션 데이터 반환
};
4. mission.controller.js
- 클라이언트의 요청을 받아 listStoreMissions 함수를 호출하도록 구현했습니다
// src/controllers/mission.controller.js
import { listStoreMissions } from '../services/mission.service.js';
import { StatusCodes } from 'http-status-codes';
export const handleListStoreMissions = async (req, res) => {
try {
const storeId = parseInt(req.params.storeId, 10); // URL에서 storeId 가져오기
const cursor = req.query.cursor ? parseInt(req.query.cursor, 10) : 0; // 페이지네이션을 위한 cursor 설정
const missions = await listStoreMissions(storeId, cursor);
res.status(StatusCodes.OK).json(missions);
} catch (error) {
console.error(error);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: '미션 목록 조회에 실패했습니다.' });
}
};
5. index.js
- 특정 가게의 미션 목록 조회 API를 설정했습니다.
// 특정 가게의 미션 목록 조회 API
app.get('/api/stores/:storeId/missions', handleListStoreMissions);
내가 진행 중인 미션 목록
1. mission.repository.js
- 사용자가 진행 중인 미션 목록을 데이터베이스에서 조회하는 함수를 작성했습니다.
- 이 함수는 user_missions 테이블에서 userId와 status가 IN_PROGRESS인 데이터를 가져옵니다.
// src/repositories/mission.repository.js
import { prisma } from "../db.config.js";
export const getUserInProgressMissions = async (userId, cursor = 0) => {
const missions = await prisma.user_missions.findMany({
where: {
userId: userId,
status: 'IN_PROGRESS', // 진행 중인 미션만 조회
},
select: {
mission: {
select: {
id: true,
description: true,
mission_status: true,
created_at: true,
region: {
select: { id: true, region_name: true },
},
},
},
},
orderBy: { missionId: "asc" },
take: 10,
skip: cursor,
});
return missions.map((userMission) => userMission.mission);
};
2. mission.dto.js
- 데이터베이스에서 가져온 미션 데이터를 클라이언트에 반환할 형식으로 변환하는 코드를 작성해줬습니다 (이 코드는 기존에 가게 미션 조회 때 작성한 코드와 동일하므로 따로 첨부하지 않았습니다)
// src/services/mission.service.js
import { getUserInProgressMissions } from '../repositories/mission.repository.js';
import { responseFromMissions } from '../dtos/mission.dto.js';
export const listUserInProgressMissions = async (userId, cursor) => {
const missions = await getUserInProgressMissions(userId, cursor);
return responseFromMissions(missions); // 변환된 미션 데이터 반환
};
3. mission.service.js
- getUserInProgressMissions 함수를 호출해 진행 중인 미션 목록을 가져오고, DTO로 변환하여 반환하는 서비스 함수를 구현했습니다.
// src/services/mission.service.js
import { getUserInProgressMissions } from '../repositories/mission.repository.js';
import { responseFromMissions } from '../dtos/mission.dto.js';
export const listUserInProgressMissions = async (userId, cursor) => {
const missions = await getUserInProgressMissions(userId, cursor);
return responseFromMissions(missions); // 변환된 미션 데이터 반환
};
4. mission.controller.js
- 클라이언트의 요청을 받아 listUserInProgressMissions 함수를 호출하도록 작성했습니다.
// src/controllers/mission.controller.js
import { listUserInProgressMissions } from '../services/mission.service.js';
import { StatusCodes } from 'http-status-codes';
export const handleListUserInProgressMissions = async (req, res) => {
try {
const userId = parseInt(req.params.userId, 10); // URL에서 userId 가져오기
const cursor = req.query.cursor ? parseInt(req.query.cursor, 10) : 0; // 페이지네이션을 위한 cursor 설정
const missions = await listUserInProgressMissions(userId, cursor);
res.status(StatusCodes.OK).json(missions);
} catch (error) {
console.error(error);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: '진행 중인 미션 목록 조회에 실패했습니다.' });
}
};
5. index.js
- 특정 사용자의 진행 중인 미션 목록 조회 API를 설정했습니다.
// 특정 사용자의 진행 중인 미션 목록 조회 API
app.get('/api/users/:userId/missions/in-progress', handleListUserInProgressMissions);
내가 진행 중인 미션을 진행 완료로 바꾸기
1. mission.repository.js
- userId와 missionId를 기준으로 미션 상태를 COMPLETED로 업데이트하는 함수를 구현했습니다.
// src/repositories/mission.repository.js
import { prisma } from "../db.config.js";
export const completeUserMission = async (userId, missionId) => {
return await prisma.user_missions.updateMany({
where: {
userId: userId,
missionId: missionId,
status: 'IN_PROGRESS', // 진행 중인 미션만 업데이트
},
data: {
status: 'COMPLETED',
completed_at: new Date(), // 완료 시간 기록
},
});
};
2. mission.service.js
- completeUserMission 함수를 호출하여 미션 상태를 업데이트하는 서비스 함수입니다.
// src/services/mission.service.js
import { completeUserMission } from '../repositories/mission.repository.js';
export const markMissionAsCompleted = async (userId, missionId) => {
const result = await completeUserMission(userId, missionId);
if (result.count === 0) {
throw new Error('해당 미션을 찾을 수 없거나 이미 완료된 상태입니다.');
}
return { message: '미션이 완료되었습니다.' };
};
3.mission.controller.js
- 클라이언트의 요청을 받아 markMissionAsCompleted 함수를 호출하도록 코드를 작성했습니다.
// src/controllers/mission.controller.js
import { markMissionAsCompleted } from '../services/mission.service.js';
import { StatusCodes } from 'http-status-codes';
export const handleCompleteUserMission = async (req, res) => {
try {
const userId = parseInt(req.params.userId, 10); // URL에서 userId 가져오기
const missionId = parseInt(req.params.missionId, 10); // URL에서 missionId 가져오기
const result = await markMissionAsCompleted(userId, missionId);
res.status(StatusCodes.OK).json(result);
} catch (error) {
console.error(error);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: error.message || '미션 완료 처리에 실패했습니다.' });
}
};
4. index.js
- 특정 사용자의 미션을 완료로 변경하는 API 경로를 설정했습니다.
// src/index.js
import express from 'express';
import { handleCompleteUserMission } from './controllers/mission.controller.js';
const app = express();
// 특정 사용자의 진행 중인 미션 완료로 업데이트 API
app.put('/api/users/:userId/missions/:missionId/complete', handleCompleteUserMission);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
테스트 스크린샷 (test.http로 리퀘스트 보내고 확인)
이 게시물을 참고하였습니다⬇️
https://velog.io/@iberis/REST-Client-VS-code-에서-http-request-보내는-익스텐션
[node.js] VS code 에서 http request 보내기
REST Client 익스텐션 설치.http 확장자 파일 생성대문자 method url 작성 후 Send Request 버튼 클릭각 요청 사이에 - request body 는 한 줄 띄고 { }\` 안에 작성
velog.io
- 내가 작성한 리뷰 목록 테스트
### 내 리뷰 목록 조회
GET <http://localhost:3000/api/users/1/reviews?cursor>
Content-Type: application/json
☑️실행결과

2. 특정 가게의 미션 목록
###특정 가게 미션 조회
GET <http://localhost:3000/api/stores/1/missions?cursor>
Content-Type: application/json
☑️실행결과

3. 내가 진행중인 미션 목록 조회
###내가 진행중인 미션목록 조회
GET <http://localhost:3000/api/users/1/missions/in-progress?cursor=0>
Content-Type: application/json
☑️ 실행결과

4. 내가 진행 중인 미션을 진행 완료로 바꾸기
PUT http://localhost:3000/api/users/1/missions/1/complete
Content-Type: application/json
//리소스의 일부 속성을 업데이트하는 것이기 때문에 put을 사용했습니다.
☑️ 실행결과


⚡6주차 트러블 슈팅 기록
⚡트러블 슈팅 기록
NO.1
이슈
👉 Argument storeId: Invalid value provided. Expected IntFilter or Int, provided String 오류 발생
Argument storeId: Invalid value provided. Expected IntFilter or Int, provided String.
at Dn (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:114:8082)
at Mn.handleRequestError (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:121:7396)
at Mn.handleAndLogRequestError (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:121:7061)
at Mn.request (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:121:6745)
at async l (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:130:9633)
at async getAllStoreReviews (file:///D:/umc-week5/src/repositories/user.repository.js:54:19)
at async listStoreReviews (file:///D:/umc-week5/src/services/store.service.js:21:19)
at async handleListStoreReviews (file:///D:/umc-week5/src/controller/store.controller.js:17:19) {
clientVersion: '5.21.1'
}
문제
👉
- Prisma에서 storeId가 Int 형식으로 지정되어 있는데, 문자열로 제공했기 때문에 발생
- storeId는 Int 타입이므로 Prisma가 문자열 "1"을 받아들이지 못하고 오류를 발생시켰던 것
해결
👉 storeId를 정수로 변환:
storeId가 문자열이 아니라 정수로 전달되도록 컨트롤러나 서비스 레이어에서 변환
export const handleListStoreReviews = async (req, res, next) => {
try {
const storeId = parseInt(req.params.storeId, 10); // storeId를 정수로 변환
const cursor = req.query.cursor ? parseInt(req.query.cursor, 10) : 0; // cursor 기본값 설정
const reviews = await listStoreReviews(storeId, cursor);
res.status(StatusCodes.OK).json(reviews);
} catch (error) {
console.error(error);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: '리뷰 조회에 실패했습니다.' });
}
};
NO.2
이슈
👉 user와 store를 null로 리턴하는 현상
TTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Content-Length: 26
ETag: W/"1a-cu5+mc44qDfmQ9TfrCaIxJwtVWU"
Date: Sun, 27 Oct 2024 11:43:09 GMT
Connection: close
{
"user": null,
"store": null
}
문제
👉
- responseFromReviews 함수가 제대로 호출되지 않고, 단일 리뷰 객체에 대한 DTOresponseFromReview가 직접 호출됨.
- listStoreReviews에서 잘못된 데이터가 반환되었기 때문에 발생한 현상
해결
👉 listStoreReviews 함수에서 responseFromReviews가 아닌, responseFromReview가 잘못 호출된 것이 아닌지 확인했습니다.
→ 확인 결과 잘못 호출하였다는 걸 알게 되었고, 바로 수정하였습니다.
NO.3
이슈
👉
PrismaClientValidationError:
Invalid prisma.missions.findMany() invocation:
{
select: {
id: true,
description: true,
mission_status: true,
created_at: true,
region: {
select: {
id: true,
region_name: true
}
}
},
where: {
storeId: 1,
~~~~~~~
? AND?: missionsWhereInput | missionsWhereInput[],
? OR?: missionsWhereInput[],
? NOT?: missionsWhereInput | missionsWhereInput[],
? id?: IntFilter | Int,
? regionId?: IntFilter | Int,
? mission_status?: EnumMissionStatusFilter | MissionStatus,
? description?: StringFilter | String,
? created_at?: DateTimeFilter | DateTime,
? region?: RegionsRelationFilter | regionsWhereInput,
? user_missions?: User_missionsListRelationFilter
},
orderBy: {
id: "asc"
},
take: 5
}
Unknown argument storeId. Available options are marked with ?.
at Dn (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:114:8082)
at Mn.handleRequestError (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:121:7396)
at Mn.handleAndLogRequestError (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:121:7061)
at Mn.request (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:121:6745)
at async l (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:130:9633)
at async getStoreMissions (file:///D:/umc-week5/src/repositories/mission.repository.js:17:20)
at async listStoreMissions (file:///D:/umc-week5/src/services/mission.service.js:15:22)
at async handleListStoreMissions (file:///D:/umc-week5/src/controller/mission.controller.js:19:22) {
clientVersion: '5.21.1'
}
PrismaClientValidationError:
Invalid prisma.missions.findMany() invocation:
{
select: {
id: true,
description: true,
mission_status: true,
created_at: true,
region: {
select: {
id: true,
region_name: true
}
}
},
where: {
storeId: 1,
~~~~~~~
? AND?: missionsWhereInput | missionsWhereInput[],
? OR?: missionsWhereInput[],
? NOT?: missionsWhereInput | missionsWhereInput[],
? id?: IntFilter | Int,
? regionId?: IntFilter | Int,
? mission_status?: EnumMissionStatusFilter | MissionStatus,
? description?: StringFilter | String,
? created_at?: DateTimeFilter | DateTime,
? region?: RegionsRelationFilter | regionsWhereInput,
? user_missions?: User_missionsListRelationFilter
},
orderBy: {
id: "asc"
},
take: 5
}
Unknown argument storeId. Available options are marked with ?.
at Dn (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:114:8082)
at Mn.handleRequestError (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:121:7396)
at Mn.handleAndLogRequestError (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:121:7061)
at Mn.request (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:121:6745)
at async l (D:\\umc-week5\\node_modules\\@prisma\\client\\runtime\\library.js:130:9633)
at async getStoreMissions (file:///D:/umc-week5/src/repositories/mission.repository.js:17:20)
at async listStoreMissions (file:///D:/umc-week5/src/services/mission.service.js:15:22)
at async handleListStoreMissions (file:///D:/umc-week5/src/controller/mission.controller.js:19:22) {
clientVersion: '5.21.1'
}
문제
👉missions 모델에 storeId 필드가 존재하지 않기 때문에 발생!
해결
👉
스키마에서 missions가 regions나 stores와 연결된 방식에 따라 적절히 수정하였습니다. 또한 MYSQL도 같이 수정해주었습니다,
MYSQL 수정
-- `missions` 테이블에 `storeId` 컬럼 추가
ALTER TABLE missions
ADD COLUMN store_id INT;
-- `missions.store_id`를 `stores.id`와 연결하는 외래 키 제약 조건 추가
ALTER TABLE missions
ADD CONSTRAINT fk_store_missions
FOREIGN KEY (store_id) REFERENCES stores(id)
ON DELETE SET NULL; -- 스토어 삭제 시 NULL로 설정 (필요에 따라 설정 가능)
Prisma 수정
// stores 모델
model stores {
id Int @id @default(autoincrement())
store_name String @db.VarChar(100)
region regions? @relation(fields: [regionId], references: [id])
regionId Int? @map("region_id")
store_address String? @db.VarChar(255)
reviews reviews[]
user_missions user_missions[]
missions missions[] // stores와 missions 간의 관계 설정
@@map("stores")
}
// missions 모델
model missions {
id Int @id @default(autoincrement())
description String @db.Text
mission_status MissionStatus
created_at DateTime @default(now()) @db.Timestamp(6)
region regions? @relation(fields: [regionId], references: [id])
regionId Int? @map("region_id")
store stores? @relation(fields: [storeId], references: [id]) // stores와 관계 설정
storeId Int? @map("store_id") // stores의 외래 키
user_missions user_missions[]
@@map("missions")
}
참고 레퍼런스
NO.4
이슈
👉 깃 이슈 발생
$ git checkout feature/mission-06
error: Your local changes to the following files would be overwritten by checkout:
package-lock.json
package.json
src/controller/challenge.controller.js
src/controller/mission.controller.js
src/controller/review.controller.js
src/controller/store.controller.js
src/controller/user.controller.js
src/db.config.js
src/dtos/challenge.dto.js
src/dtos/mission.dto.js
src/dtos/review.dto.js
src/dtos/store.dto.js
src/dtos/user.dto.js
src/index.js
src/repositories/challenge.repository.js
src/repositories/mission.repository.js
src/repositories/review.repository.js
src/repositories/store.repository.js
src/repositories/user.repository.js
src/services/challenge.service.js
src/services/mission.service.js
src/services/review.service.js
src/services/store.service.js
src/services/user.service.js
test.http
Please commit your changes or stash them before you switch branches.
Aborting
문제
👉
feature/mission-06 브랜치로 체크아웃이 성공했으나 git log를 보면 여전히 first commit만 있는 상태
또, 추가된 파일들(prisma/migrations/...)이 있으며, 이전 브랜치에서의 모든 파일들이 현재 커밋에 포함되지 않은 상태
해결
👉 git log를 활용하여 커밋로그를 본 다음 수정
ommit ac2dc78b90046b77a1e9cf71ffd2c1c34766c7bf (HEAD -> feature/mission-06, origin/main, origin/feature/mission-06)
Author: gimchaewon <gimchaewon417@gmail.com>
Date: Mon Oct 14 12:03:20 2024 +0900
first commit
# 먼저 feature/mission-05 브랜치로 돌아가서 확인
git checkout feature/mission-05
# feature/mission-06 브랜치로 돌아가기
git checkout feature/mission-06
# feature/mission-05의 변경 사항을 feature/mission-06으로 병합
git merge feature/mission-05
참고 레퍼런스
NO.5
이슈
👉 ReferenceError: router is not defined 오류 발생
file:///D:/umc-week5/src/index.js:33
router.get('/api/users/:userId/reviews', handleListUserReviews);
^
ReferenceError: router is not defined
at file:///D:/umc-week5/src/index.js:33:1
at ModuleJob.run (node:internal/modules/esm/module_job:222:25)
at async ModuleLoader.import (node:internal/modules/esm/loader:316:24)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:123:5)
문제
👉 이 오류는 router 변수가 선언되지 않았기 때문에 발생!
해결
👉router를 사용하지 않고 그냥 app로 일단 구현했습니다. 추후에 router를 사용하는 방식으로 바꿀 예정입니다.
// src/index.js
import express from "express";
import cors from "cors";
import { handleStoreSignUp,handleListStoreReviews} from "./controller/store.controller.js";
import { handleReviewSignUp } from "./controller/review.controller.js";
import { handleMissionSignUp,handleListStoreMissions,handleListUserInProgressMissions,handleCompleteUserMission } from "./controller/mission.controller.js";
import { handleChallengeSignUp } from "./controller/challenge.controller.js";
import { handleListUserReviews } from './controller/user.controller.js';
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.use(cors());
// 가게 추가 API
app.post("/api/stores", handleStoreSignUp);
// 가게 리뷰 추가 API
app.post("/api/reviews", handleReviewSignUp);
// 가게 미션 추가 API
app.post("/api/missions", handleMissionSignUp);
// 가게의 미션 도전하기 API
app.post("/api/challenges", handleChallengeSignUp);
// 리뷰 목록 확인
app.get("/api/stores/:storeId/reviews", handleListStoreReviews)
// 내가 작성한 리뷰 목록 확인
app.get('/api/users/:userId/reviews', handleListUserReviews);
// 특정 가게의 미션 목록 조회 API
app.get('/api/stores/:storeId/missions', handleListStoreMissions);
// 특정 사용자의 진행 중인 미션 목록 조회 API
app.get('/api/users/:userId/missions/in-progress', handleListUserInProgressMissions);
// 특정 사용자의 진행 중인 미션 완료로 업데이트 API
app.put('/api/users/:userId/missions/:missionId/complete', handleCompleteUserMission);
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
📢 6주차 학습 후기
😺: 이번주 핵심 키워드는 ORM이었는데 전에 프로젝트를 할 때 ORM을 사용했던 경험이 있어서 이번 워크북 작성 때 이를 리마인드 할 수 있어서 좋았습니다. 또한 다양한 ORM에 대해서 알 수 있어서 좋았습니다.
'{Extracurricular Activities} > UMC 7기 - Node.js' 카테고리의 다른 글
| [UMC 7th Server] Chapter 8. 프론트엔드 연동과 Swagger (2) | 2024.11.18 |
|---|---|
| [UMC 7th Server] Chapter 7. Express 미들웨어 & API 응답 통일 & 에러 핸들링 (3) | 2024.11.12 |
| [UMC 7th Server] Chapter 5. ES6와 프로젝트 파일 구조의 이해 (4) | 2024.10.28 |
| [UMC 7th Server] Chapter 4. ES6와 프로젝트 파일 구조의 이해 (1) | 2024.10.28 |
| [UMC 7th Server] Chapter 3. API URL의 설계 & 프로젝트 세팅 (1) | 2024.10.10 |