728x90
Chapter 8. 프론트엔드 연동과 Swagger
☑️ 실습 인증
💼 Swagger 설정하기
더보기



👉 Swagger 관련 라이브러리 설치
다음 명령어들을 입력하여 라이브러리를 설치해줬습니다.
npm add \\
swagger-autogen \\
swagger-ui-express

👉 Swagger 세팅
다음과 같이 index.js를 설정해줬습니다.
// src/index.js
import express from "express";
import cors from "cors";
import swaggerAutogen from "swagger-autogen";
import swaggerUiExpress from "swagger-ui-express";
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());
app.use((req, res, next) => {
res.success = (success) => {
return res.json({ resultType: "SUCCESS", error: null, success });
};
res.error = ({ errorCode = "unknown", reason = null, data = null }) => {
return res.json({
resultType: "FAIL",
error: { errorCode, reason, data },
success: null,
});
};
next();
});
app.use(
"/docs",
swaggerUiExpress.serve,
swaggerUiExpress.setup({}, {
swaggerOptions: {
url: "/openapi.json",
},
})
);
app.get("/openapi.json", async (req, res, next) => {
// #swagger.ignore = true
const options = {
openapi: "3.0.0",
disableLogs: true,
writeOutputFile: false,
};
const outputFile = "/dev/null"; // 파일 출력은 사용하지 않습니다.
const routes = ["./src/index.js"];
const doc = {
info: {
title: "UMC 7th",
description: "UMC 7th Node.js 테스트 프로젝트입니다.",
},
host: "localhost:3000",
};
const result = await swaggerAutogen(options)(outputFile, routes, doc);
res.json(result ? result.data : null);
});
// 가게 추가 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.use((err, req, res, next) => {
if (res.headersSent) {
return next(err);
}
res.status(err.statusCode || 500).error({
errorCode: err.errorCode || "unknown",
reason: err.reason || err.message || null,
data: err.data || null,
});
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
/docs 경로로 들어가서 다음과 같이 swagger가 실행되는 것을 확인했습니다.

👉Swagger 확인하기
다음과 같이 swagger로 body 부분을 확인할 수 있었습니다.

🎯핵심 키워드 정리
💠Swagger란?
- RESTful API를 설계하고 문서화하기 위한 도구 모음
- API의 엔드포인트, 요청/응답 구조, 파라미터 등을 YAML이나 JSON 형식으로 작성해 인터랙티브한 문서를 자동 생성한다.
- 클라이언트나 다른 개발자와 더 쉽게 협업할 수 있게 도와준다
💠 OpenAPI
- RESTful API를 설계하고 문서화하기 위한 표준화된 사양
- API의 구조와 동작 방식을 정의하기 위해 YAML이나 JSON 형식을 사용하며, 이를 통해 API 문서를 자동 생성하고, 개발 및 유지보수를 간편하게한다.
💠 OpenAPI 버전 별 특징 및 주요 차이점
2.0 | 기본적인 REST API 구조와 보안 정의 지원 | Swagger에서 OpenAPI로의 전환점 |
3.0 | components, oneOf/anyOf, 다중 파일 업로드, 헤더 인증 | 문서화 유연성 증가 |
3.1 | JSON Schema 호환성, webhooks, 비동기 처리 지원 | 최신 JSON 표준 및 비동기 지원 강화 |
💠 OpenAPI Component
components:
schemas:공통 데이터 모델 (예: User, Product 등)을 정의하여 여러 곳에서 재사용 가능
parameters:공통 파라미터 (예: userId, storeId 등)를 정의해 여러 경로에서 재사용
securitySchemes:인증 방식 (API Key, OAuth 등)을 정의해 여러 엔드포인트에서 일관된 인증 처리
requestBodies:
responses:자주 쓰이는 응답 (예: 404 Not Found, 500 Internal Server Error 등)을 미리 정의
headers: API 요청이나 응답에 공통으로 사용되는 헤더를 정
examples: 요청과 응답의 예시 데이터를 정의해 개발자가 요청이나 응답 구조를 쉽게 이해할 수 있게한다
links: API 응답을 기반으로 다른 관련된 API로 연결할 때 사용할 수 있다
callbacks: 비동기 API에서 콜백 URL을 정의하여 서버가 작업 완료 후 클라이언트에 알림을 보낼 수 있다
✏️8주차 필기
🔥 미션 인증
💻 Swagger 문서
더보기











💠전체적인 결과
다음과 같이 나눠주었습니다 (크게 Store, Reviews, Missons ,Challenge, Users)

1. review 수정
review.controller 수정
import { StatusCodes } from "http-status-codes";
import { bodyToReview } from "../dtos/review.dto.js";
import { reviewSignUp } from "../services/review.service.js";
export const handleReviewSignUp = async (req, res, next) => {
/*
#swagger.summary = '리뷰 작성 API';
#swagger.description = '사용자가 특정 상점에 리뷰를 작성하는 API입니다.';
#swagger.tags = ['Reviews'];
#swagger.requestBody = {
description: '리뷰 등록을 위한 요청 데이터',
required: true,
content: {
"application/json": {
schema: {
type: "object",
properties: {
userId: {
type: "integer",
description: "리뷰를 작성하는 사용자의 ID",
example: 1
},
storeId: {
type: "integer",
description: "리뷰가 작성될 상점의 ID",
example: 1
},
rating: {
type: "integer",
description: "상점에 대한 평점 (1~5 사이의 값)",
example: 5
},
reviewText: {
type: "string",
description: "리뷰 내용",
example: "Amazing coffee and cozy atmosphere!"
}
}
}
}
}
};
#swagger.responses[201] = {
description: "리뷰 등록 성공",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
error: { type: "object", nullable: true, example: null },
success: {
type: "object",
properties: {
reviewId: { type: "integer", example: 1 },
userId: { type: "integer", example: 1 },
storeId: { type: "integer", example: 1 },
rating: { type: "integer", example: 5 },
reviewText: { type: "string", example: "Amazing coffee and cozy atmosphere!" },
createdAt: { type: "string", format: "date-time", example: "2024-10-28T13:01:39.000Z" },
store: {
type: "object",
properties: {
storeName: { type: "string", example: "Test Store" }
}
}
}
}
}
}
}
}
};
#swagger.responses[400] = {
description: "잘못된 요청 데이터",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "FAIL" },
error: { type: "string", example: "Invalid request data" }
}
}
}
}
};
#swagger.responses[500] = {
description: "서버 오류",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "ERROR" },
error: { type: "string", example: "Internal server error" }
}
}
}
}
};
*/
try {
const review = await reviewSignUp(bodyToReview(req.body));
res.status(StatusCodes.CREATED).success(review);
} catch (error) {
next(error);
}
};
✏️결과

2. Store 수정
store.controller 수정
import { StatusCodes } from "http-status-codes";
import { bodyToStore } from "../dtos/store.dto.js";
import { storeSignUp, listStoreReviews } from "../services/store.service.js";
export const handleStoreSignUp = async (req, res, next) => {
/*
#swagger.summary = '상점 등록 API';
#swagger.description = '새로운 상점을 등록하는 API입니다.';
#swagger.tags = ['Stores'];
#swagger.requestBody = {
description: '상점 등록에 필요한 데이터',
required: true,
content: {
"application/json": {
schema: {
type: "object",
properties: {
storeName: { type: "string", description: "상점 이름", example: "Awesome Cafe" },
address: { type: "string", description: "상점 주소", example: "123 Coffee Street" },
regionId: { type: "integer", description: "지역 ID", example: 1 }
}
}
}
}
};
#swagger.responses[201] = {
description: "상점 등록 성공",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
error: { type: "object", nullable: true, example: null },
success: {
type: "object",
properties: {
storeId: { type: "integer", example: 1 },
storeName: { type: "string", example: "Awesome Cafe" },
address: { type: "string", example: "123 Coffee Street" },
regionId: { type: "integer", example: 1 },
createdAt: { type: "string", format: "date-time", example: "2024-10-28T13:01:39.000Z" }
}
}
}
}
}
}
};
#swagger.responses[400] = {
description: "잘못된 요청 데이터",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "FAIL" },
error: { type: "string", example: "Invalid request data" }
}
}
}
}
};
*/
try {
const store = await storeSignUp(bodyToStore(req.body));
res.status(StatusCodes.CREATED).success(store);
} catch (error) {
next(error); // 전역 오류 핸들러로 오류 전달
}
};
export const handleListStoreReviews = async (req, res, next) => {
/*
#swagger.summary = '상점 리뷰 목록 조회 API';
#swagger.description = '특정 상점의 리뷰 목록을 조회하는 API입니다.';
#swagger.tags = ['Stores'];
#swagger.parameters['storeId'] = {
in: 'path',
description: '조회할 상점의 ID',
required: true,
schema: {
type: 'integer',
example: 1
}
};
#swagger.parameters['cursor'] = {
in: 'query',
description: '페이지네이션을 위한 커서',
required: false,
schema: {
type: 'integer',
example: 0
}
};
#swagger.responses[200] = {
description: "리뷰 목록 조회 성공",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
error: { type: "object", nullable: true, example: null },
success: {
type: "object",
properties: {
data: {
type: "array",
items: {
type: "object",
properties: {
reviewId: { type: "integer", example: 1 },
userId: { type: "integer", example: 1 },
storeId: { type: "integer", example: 1 },
rating: { type: "integer", example: 5 },
reviewText: { type: "string", example: "Great place!" },
createdAt: { type: "string", format: "date-time", example: "2024-10-28T13:01:39.000Z" },
user: {
type: "object",
properties: {
userName: { type: "string", example: "testuser1" },
email: { type: "string", example: "testuser1@example.com" },
gender: { type: "string", example: "남성" },
birth: { type: "string", format: "date", example: "1990-01-01T00:00:00.000Z" },
address: { type: "string", example: "Seoul, South Korea" },
detailAddress: { type: "string", example: "101-202" },
phoneNumber: { type: "string", example: "010-1234-5678" }
}
},
store: {
type: "object",
properties: {
storeName: { type: "string", example: "Test Store" }
}
}
}
}
},
pagination: {
type: "object",
properties: {
cursor: { type: "integer", nullable: true, example: 4 }
}
}
}
}
}
}
}
}
};
#swagger.responses[400] = {
description: "잘못된 요청 데이터",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "FAIL" },
error: { type: "string", example: "Invalid storeId" }
}
}
}
}
};
*/
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).success(reviews);
} catch (error) {
next(error);
}
};
✏️결과


3. Misson 수정
mission controller
import { StatusCodes } from "http-status-codes";
import {
missionSignUp,
listStoreMissions,
listUserInProgressMissions,
markMissionAsCompleted
} from "../services/mission.service.js";
import { bodyToMission } from "../dtos/mission.dto.js";
export const handleMissionSignUp = async (req, res, next) => {
/*
#swagger.summary = '미션 등록 API';
#swagger.description = '새로운 미션을 등록하는 API입니다.';
#swagger.tags = ['Missions'];
#swagger.requestBody = {
description: '미션 등록에 필요한 데이터',
required: true,
content: {
"application/json": {
schema: {
type: "object",
properties: {
regionId: { type: "integer", description: "지역 ID", example: 1 },
missionStatus: { type: "string", description: "미션 상태", example: "IN_PROGRESS" },
description: { type: "string", description: "미션 설명", example: "Discover Seoul's best coffee shops" }
}
}
}
}
};
#swagger.responses[201] = {
description: "미션 등록 성공",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
success: {
type: "object",
properties: {
missionId: { type: "integer", example: 1 },
regionId: { type: "integer", example: 1 },
missionStatus: { type: "string", example: "IN_PROGRESS" },
description: { type: "string", example: "Discover Seoul's best coffee shops" },
createdAt: { type: "string", format: "date-time", example: "2024-10-28T13:01:39.000Z" }
}
}
}
}
}
}
};
*/
try {
const mission = await missionSignUp(bodyToMission(req.body));
res.status(StatusCodes.CREATED).success(mission);
} catch (error) {
next(error);
}
};
export const handleListStoreMissions = async (req, res, next) => {
/*
#swagger.summary = '상점 미션 목록 조회 API';
#swagger.description = '특정 상점에서 진행 중인 미션 목록을 조회합니다.';
#swagger.tags = ['Missions'];
#swagger.parameters['storeId'] = {
in: 'path',
description: '조회할 상점의 ID',
required: true,
schema: { type: 'integer', example: 1 }
};
#swagger.parameters['cursor'] = {
in: 'query',
description: '페이지네이션 커서',
required: false,
schema: { type: 'integer', example: 0 }
};
#swagger.responses[200] = {
description: "미션 목록 조회 성공",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
success: {
type: "array",
items: {
type: "object",
properties: {
missionId: { type: "integer", example: 1 },
title: { type: "string", example: "첫 번째 미션" },
description: { type: "string", example: "미션 설명" },
pointsReward: { type: "integer", example: 100 }
}
}
}
}
}
}
}
};
*/
try {
const storeId = parseInt(req.params.storeId, 10);
const cursor = req.query.cursor ? parseInt(req.query.cursor, 10) : 0;
const missions = await listStoreMissions(storeId, cursor);
res.status(StatusCodes.OK).success(missions);
} catch (error) {
next(error);
}
};
export const handleListUserInProgressMissions = async (req, res, next) => {
/*
#swagger.summary = '진행 중인 사용자 미션 목록 조회 API';
#swagger.description = '사용자가 진행 중인 미션의 목록을 조회합니다.';
#swagger.tags = ['Missions'];
#swagger.parameters['userId'] = {
in: 'path',
description: '사용자 ID',
required: true,
schema: { type: 'integer', example: 1 }
};
#swagger.parameters['cursor'] = {
in: 'query',
description: '페이지네이션 커서',
required: false,
schema: { type: 'integer', example: 0 }
};
#swagger.responses[200] = {
description: "진행 중인 미션 목록 조회 성공",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
success: {
type: "array",
items: {
type: "object",
properties: {
missionId: { type: "integer", example: 1 },
status: { type: "string", example: "IN_PROGRESS" },
description: { type: "string", example: "Discover Seoul's best coffee shops" }
}
}
}
}
}
}
}
};
*/
try {
const userId = parseInt(req.params.userId, 10);
const cursor = req.query.cursor ? parseInt(req.query.cursor, 10) : 0;
const missions = await listUserInProgressMissions(userId, cursor);
res.status(StatusCodes.OK).success(missions);
} catch (error) {
next(error);
}
};
export const handleCompleteUserMission = async (req, res, next) => {
/*
#swagger.summary = '사용자 미션 완료 API';
#swagger.description = '사용자가 진행 중인 미션을 완료 상태로 변경합니다.';
#swagger.tags = ['Missions'];
#swagger.parameters['userId'] = {
in: 'path',
description: '사용자 ID',
required: true,
schema: { type: 'integer', example: 1 }
};
#swagger.parameters['missionId'] = {
in: 'path',
description: '완료할 미션 ID',
required: true,
schema: { type: 'integer', example: 1 }
};
#swagger.responses[200] = {
description: "미션 완료 처리 성공",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
success: {
type: "object",
properties: {
missionId: { type: "integer", example: 1 },
userId: { type: "integer", example: 1 },
status: { type: "string", example: "COMPLETED" }
}
}
}
}
}
}
};
*/
try {
const userId = parseInt(req.params.userId, 10);
const missionId = parseInt(req.params.missionId, 10);
const result = await markMissionAsCompleted(userId, missionId);
res.status(StatusCodes.OK).success(result);
} catch (error) {
next(error);
}
};
✏️결과





4. Challenge 수정
challenge.controller
import { StatusCodes } from "http-status-codes";
import { bodyToChallenge } from "../dtos/challenge.dto.js";
import { challengeSignUp } from "../services/challenge.service.js";
export const handleChallengeSignUp = async (req, res, next) => {
/*
#swagger.summary = '챌린지 등록 API';
#swagger.description = '새로운 챌린지를 등록하는 API입니다.';
#swagger.tags = ['Challenges'];
#swagger.requestBody = {
description: '챌린지 등록에 필요한 데이터',
required: true,
content: {
"application/json": {
schema: {
type: "object",
properties: {
userId: { type: "integer", description: "사용자 ID", example: 1 },
missionId: { type: "integer", description: "미션 ID", example: 1 },
storeId: { type: "integer", description: "상점 ID", example: 1 },
status: { type: "string", description: "챌린지 상태", example: "IN_PROGRESS" }
}
}
}
}
};
#swagger.responses[201] = {
description: "챌린지 등록 성공",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
success: {
type: "object",
properties: {
challengeId: { type: "integer", example: 1 },
userId: { type: "integer", example: 1 },
missionId: { type: "integer", example: 1 },
storeId: { type: "integer", example: 1 },
status: { type: "string", example: "IN_PROGRESS" },
createdAt: { type: "string", format: "date-time", example: "2024-10-28T13:01:39.000Z" }
}
}
}
}
}
}
};
#swagger.responses[400] = {
description: "잘못된 요청 데이터",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "FAIL" },
error: { type: "string", example: "Invalid request data" }
}
}
}
}
};
#swagger.responses[500] = {
description: "서버 오류",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "ERROR" },
error: { type: "string", example: "Internal server error" }
}
}
}
}
};
*/
try {
const challenge = await challengeSignUp(bodyToChallenge(req.body));
res.status(StatusCodes.CREATED).success(challenge);
} catch (error) {
next(error);
}
};
✏️결과

5. Users 수정
user.controller
import { StatusCodes } from "http-status-codes";
import { bodyToUser } from "../dtos/user.dto.js";
import { userSignUp, listUserReviews } from "../services/user.service.js";
export const handleUserSignUp = async (req, res, next) => {
/*
#swagger.summary = '사용자 회원가입 API';
#swagger.description = '새로운 사용자를 등록하는 API입니다.';
#swagger.tags = ['Users'];
#swagger.requestBody = {
description: '사용자 회원가입에 필요한 데이터',
required: true,
content: {
"application/json": {
schema: {
type: "object",
properties: {
userName: { type: "string", description: "사용자 이름", example: "John Doe" },
email: { type: "string", description: "사용자 이메일", example: "johndoe@example.com" },
password: { type: "string", description: "사용자 비밀번호", example: "password123" },
gender: { type: "string", description: "성별", example: "남성" },
birth: { type: "string", format: "date", description: "생년월일", example: "1990-01-01" },
address: { type: "string", description: "주소", example: "Seoul, South Korea" },
phoneNumber: { type: "string", description: "전화번호", example: "010-1234-5678" }
}
}
}
}
};
#swagger.responses[200] = {
description: "사용자 회원가입 성공",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
success: {
type: "object",
properties: {
userId: { type: "integer", example: 1 },
userName: { type: "string", example: "John Doe" },
email: { type: "string", example: "johndoe@example.com" },
createdAt: { type: "string", format: "date-time", example: "2024-10-28T13:01:39.000Z" }
}
}
}
}
}
}
};
#swagger.responses[400] = {
description: "잘못된 요청 데이터",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "FAIL" },
error: { type: "string", example: "Invalid request data" }
}
}
}
}
};
#swagger.responses[500] = {
description: "서버 오류",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "ERROR" },
error: { type: "string", example: "Internal server error" }
}
}
}
}
};
*/
try {
const user = await userSignUp(bodyToUser(req.body));
res.status(StatusCodes.OK).success(user);
} catch (error) {
next(error);
}
};
export const handleListUserReviews = async (req, res, next) => {
/*
#swagger.summary = '사용자 리뷰 목록 조회 API';
#swagger.description = '특정 사용자가 작성한 리뷰 목록을 조회하는 API입니다.';
#swagger.tags = ['Users'];
#swagger.parameters['userId'] = {
in: 'path',
description: '조회할 사용자 ID',
required: true,
schema: { type: 'integer', example: 1 }
};
#swagger.parameters['cursor'] = {
in: 'query',
description: '페이지네이션을 위한 커서',
required: false,
schema: { type: 'integer', example: 0 }
};
#swagger.responses[200] = {
description: "리뷰 목록 조회 성공",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "SUCCESS" },
success: {
type: "array",
items: {
type: "object",
properties: {
reviewId: { type: "integer", example: 1 },
storeId: { type: "integer", example: 1 },
rating: { type: "integer", example: 5 },
reviewText: { type: "string", example: "Amazing coffee and cozy atmosphere!" },
createdAt: { type: "string", format: "date-time", example: "2024-10-28T13:01:39.000Z" },
store: {
type: "object",
properties: {
storeName: { type: "string", example: "Test Store" }
}
}
}
}
}
}
}
}
}
};
#swagger.responses[400] = {
description: "잘못된 요청 데이터",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "FAIL" },
error: { type: "string", example: "Invalid userId" }
}
}
}
}
};
#swagger.responses[500] = {
description: "서버 오류",
content: {
"application/json": {
schema: {
type: "object",
properties: {
resultType: { type: "string", example: "ERROR" },
error: { type: "string", example: "Internal server error" }
}
}
}
}
};
*/
try {
const userId = parseInt(req.params.userId, 10);
const cursor = req.query.cursor ? parseInt(req.query.cursor, 10) : 0;
const reviews = await listUserReviews(userId, cursor);
res.status(StatusCodes.OK).success(reviews);
} catch (error) {
next(error);
}
};
✏️결과

728x90
'🏃♀️ 대외활동 > UMC 7기 - Node.js' 카테고리의 다른 글
[UMC 7th Server] Chapter 9. 로그인 및 회원 가입 기능 구현 (1) | 2024.11.24 |
---|---|
[UMC 7th Server] 6, 7주차 트러블 슈팅 기록 (0) | 2024.11.19 |
[UMC 7th Server] Chapter 7. Express 미들웨어 & API 응답 통일 & 에러 핸들링 (3) | 2024.11.12 |
[UMC 7th Server] Chapter 6. ORM 사용해보기 (5) | 2024.11.07 |
[UMC 7th Server] Chapter 5. ES6와 프로젝트 파일 구조의 이해 (4) | 2024.10.28 |