내일배움캠프 스파르타 코딩클럽
저번 입문 개인과제(1) 에 이어서 TIL을 작성하도록 하겠다.
저번 내용은 패키지매니저 명령어들과 프로젝트에 설치한 패키지들 구성을 알아보았고
이번에는 app, schemas, router, middleware 4가지 를 살펴보도록 하겠다.
살펴 보기에 앞서 어떠한것 들을 구현 했는지 잘 작동이 되었는지는 피드백으로 아래 와같이 확인해 볼 수 있다.
총평
- 대부분의 기능이 잘 작동합니다.
- 처음 시작하셨을 떄보다 실력이 더 많이 향상된 것이 보입니다.
- 추가적으로 시간에 대한 고민까지 하셨다니 멋집니다.
- 다만 코드를 작성함에 있어 어떻게 효율적으로 작성할 수 있을지에 대한 고민은 필요합니다만 이는 시간이 흐르며 자연스레 나아질 부분입니다.
- 수고하셨습니다.
제일 핵심이자 메인보드를 담당하는 app.js
import express from 'express';
import connect from './src/schemas/index.js';
import GoodsRouter from './src/routers/GoodsRouter.js';
import errorHandlerMiddleware from './src/middlewarmies/error.handler.middleware.js';
const app = express();
const PORT = 3300;
connect();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const router = express.Router();
router.get('/', (req, res, next) => {
return res.json({ message: 'Welcome to MDshop' });
});
app.use('/api', [router, GoodsRouter]);
app.use(errorHandlerMiddleware);
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
제일 애용하는 3000 번 포트는 todolist 서버 포트로 사용하고 있으므로 위와같이 3300번으로 설정한 것을 볼 수 있다.
mongoDB 사용을 위해 connect 라는 이름으로 임포트 하여 connect() 로 실행을 시켜주었고
app.use('/api', [GoodRouter]) 를 사용하여 핵심적인 기능구현을한 파일을 실행 시켜주었다.
app.use(errorHandlerMiddleware); 를 사용하여 미들웨어를 실행 시켜주고 있다.
< schemas > <index.js>
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config();
const connect = () => {
mongoose
.connect(process.env.MONGODB_URL, {
dbName: process.env.MONGODB_NAME,
})
.catch((err) => console.log(`MongoDB 연결에 실패하였습니다. ${err}`))
.then(() => console.log('MongoDB 연결에 성공하였습니다.'));
};
mongoose.connection.on('error', (err) => {
console.error('몽고디비 연결 에러', err);
});
export default connect;
몽고디비에 연결하기 위한 작업 그리고 dotenv는 .env 파일을 통하여 key를 집어 넣어주기 위해 위와 같이 임포트 하였다.
< schemas > <GoodsSchema.js>
import mongoose from 'mongoose';
const GoodsSchema = new mongoose.Schema({
goodsId: {
// 상품ID
type: Number,
required: true,
},
goodsPw: {
// 상품PW
type: String,
required: true,
},
person: {
// 담당자
type: String,
required: true,
},
goods: {
// 상품명
type: String,
required: true,
},
manual: {
// 상품 설명
type: String,
required: true,
},
condition: {
// 상품 상태
type: String,
required: true,
},
uploadAt: {
// 생성 날짜
type: Date,
required: true,
},
updateAt: {
// 수정 날짜
type: Date,
required: false, // updateAt 필드는 필수 요소가 아닙니다.
// 완료가 되지않았다면 null 이기때문에 필수를 false로 설정
},
});
// Goods 모델 생성
export default mongoose.model('Goods', GoodsSchema);
updateAt은 수정시에 타임값을 입력하므로 필수적 요소가 아니라 false로 설정하였으며
나머지 사항들은 등록시 입력을 받거나 내가 설정을 해줘야 하는 필수적 요건이므로 true로 설정해 주었다.
< routers > < GoodsRouter.js >
import express from 'express';
import joi from 'joi';
import Goods from '../schemas/GoodsSchema.js';
const router = express.Router();
//유효성 검사에 실패했을 때, 에러가 발생해야 한다.
//검증을 진행할때 비동기적으로 진행해야 한다. .validateAsync(req.body)
const createdGoodsSchema = joi.object({
goods: joi.string().min(1).max(30).required(),
manual: joi.string().min(1).max(50).required(),
person: joi.string().min(1).max(10).required(),
goodsPw: joi.string().min(1).max(10).required(),
});
// 상품등록 API
router.post('/goods', async (req, res, next) => {
try {
// 클라이언트로 부터 받아온 value 데이터를 가져온다.
const validation = await createdGoodsSchema.validateAsync(req.body);
const { goods, manual, person, goodsPw } = validation;
// 만약, 클라이언트가 value 데이터를 전달하지 않았을 때, 클라이언트에게 에러 메시지를 전달한다.
// 해당하는 마지막 goodsId 데이터를 조회한다.
// findeOne = 1개의 데이터만 조회한다.
// sort = 정령한다. -> 어떤 컬럼을?
const goodsMaxId = await Goods.findOne().sort('-goodsId').exec();
// order 만 하면 오름차순 -order 하면 내림차순
// 몽구스로 조회할때는 exec 로 조회하면 좀더 정상적으로 조회할 수 있다.
// exec()가 없으면 프로미스로 동작하지 않게 되며 프로미스로 동작하지 않는다는건
// await 을 사용할 수 없다.
// 3. 만약 존재한다면 ID +1 하고, 아니면 1로 할당한다.
const goodsId = goodsMaxId ? goodsMaxId.goodsId + 1 : 1;
const condition = 'FOR_SALE';
const uploadAt = new Date().toLocaleString('en-US', {
timeZone: 'Asia/Seoul',
});
const updateAt = null;
// 4. 상품 등록
const loadGoods = new Goods({
goods,
manual,
person,
goodsId,
goodsPw,
condition,
uploadAt,
updateAt,
}); // 인스턴스 형식으로 만든것이고
await loadGoods.save(); // 데이터베이스에 저장한다.
// 5. 해야할 일을 클라이언트에게 반환한다.
return res.status(201).json({ loadGoods: loadGoods }); // loadGoods 없애는거 고려해보기
} catch (error) {
// 서버가 중단되지 않기위해 위를 try로 묶어주고 아래 catch 구문을 하여 에러메세지를 리스폰스 함으로 써 서버를 유지할 수 있다.
// Router 다음에 있는 에러 처리 미들웨어를 실행한다.
next(error);
}
});
// 상품 목록 조회 API
router.get('/goods', async (req, res, next) => {
try {
// 상품 목록 조회를 진행한다.
const goodsMenu = await Goods.find({}, { goodsPw: 0 })
.sort('-uploadAt')
.exec();
// mongoDB쿼리 언어규칙 find({}, { goodsPw: 0 }) 0을 넣어서 제외 1은 포함
if (!goodsMenu) {
return res.status(404).json([]);
}
// 상품 목록 조회 결과를 클라이언트에게 반환한다.
return res.status(200).json({ goodsMenu });
} catch (error) {
next(error);
}
});
// 상품 상세 조회 API
router.get('/goods/:goodsId', async (req, res, next) => {
try {
const { goodsId } = req.params;
const goodsSearch = await Goods.findOne({ goodsId }, { goodsPw: 0 }).exec();
// mongoDB쿼리 언어규칙 find({}, { goodsPw: 0 }) 0을 넣어서 제외 1은 포함
if (!goodsSearch) {
throw new Error('Goods not found');
}
// 상품 목록 조회 결과를 클라이언트에게 반환한다.
return res.status(200).json({ goodsSearch });
} catch (error) {
next(error);
}
});
const updateGoodsSchema = joi.object({
goods: joi.string().min(1).max(30),
manual: joi.string().min(1).max(50),
person: joi.string().min(1).max(10),
goodsPw: joi.string().min(1).max(10).required(),
condition: joi.string().valid('FOR_SALE', 'SOLD_OUT'),
});
// 상품 수정 비밀번호 입력 API
router.patch('/goods/:goodsId', async (req, res, next) => {
try {
const { goodsId } = req.params;
const validation = await updateGoodsSchema.validateAsync(req.body);
const { goods, manual, person, goodsPw, condition } = validation;
const updateGoods = await Goods.findOne({ goodsId }).exec();
if (!updateGoods) {
throw new Error('Goods not found');
}
if (!goodsPw) {
throw new Error('"goodsPw" is required');
}
if (goodsPw !== updateGoods.goodsPw) {
throw new Error('"goodsPw" is not same');
}
if (goods) {
updateGoods.goods = goods;
}
if (manual) {
updateGoods.manual = manual;
}
if (person) {
updateGoods.person = person;
}
if (condition) {
updateGoods.condition = condition;
}
updateGoods.updateAt = new Date().toLocaleString('en-US', {
timeZone: 'Asia/Seoul',
});
//new Date().toLocaleString("en-US", {timeZone: "Asia/Seoul"}) 이렇게 작성하면 한국시간을 가져올수 있음.
await updateGoods.save();
return res.status(200).json({ updateGoods });
} catch (error) {
next(error);
}
});
// 상품 삭제 API
router.delete('/goods/:goodsId', async (req, res, next) => {
try {
const { goodsId } = req.params;
const validation = await updateGoodsSchema.validateAsync(req.body);
const { goodsPw } = validation;
const goods = await Goods.findOne({ goodsId }).exec();
if (!goods) {
throw new Error('Goods not found');
}
if (!goodsPw) {
throw new Error('"goodsPw" is required');
}
if (goodsPw !== goods.goodsPw) {
throw new Error('"goodsPw" is not same');
}
await Goods.deleteOne({ goodsId });
return res.status(200).json({});
} catch (error) {
next(error);
}
});
export default router;
여기서 모든 error처리를 미들웨어로 넘기기 위해 thorw new Error 를사용하여 강제로 에러를 처리 하였으며
joi 같은 경우도 알아서 error로 처리해주기 때문에 유효성검사를 설정해 주었다.
이렇게 CRUD 기능이 모두 구현된 RESTful 한 설계를 완료하였다.
< middlewarmies > < error.handle.middleware.js > < 회고 >
export default (err, req, res, next) => {
console.log('에러 처리 미들웨어가 실행되었습니다.');
console.error(err);
if (err.message === 'Goods not found') {
return res.status(404).json({ errorMessage: '상품이 존재하지 않습니다.' });
}
if (err.isJoi) {
return res.status(400).json({ err: err.message });
}
if (err.message === '"goodsPw" is not same') {
return res
.status(400)
.json({ errorMessage: '(goodsPw) 비밀번호가 일치하지 않습니다.' });
}
if (err.name === 'ValidationError') {
if (err.message === '"goods" is required') {
return res
.status(400)
.json({ errorMessage: '(goods) 상품명을 입력해 주세요.' });
}
if (err.message === '"manual" is required') {
return res
.status(400)
.json({ errorMessage: '(manual) 상품 설명을 입력해 주세요.' });
}
if (err.message === '"person" is required') {
return res
.status(400)
.json({ errorMessage: '(person) 담당자를 입력해 주세요.' });
}
if (err.message === '"goodsPw" is required') {
return res
.status(400)
.json({ errorMessage: '(goodsPw) 상품 비밀번호를 입력해 주세요.' });
}
}
if (err.message === '"goodsPw" is required') {
return res
.status(400)
.json({ errorMessage: '(goodsPw) 해당 ID의 비밀번호를 입력해 주세요.' });
}
return res.status(500).json({
errorMessage: '예상치 못한 에러가 발생했습니다. 관리자에게 문의해 주세요.',
});
};
미들웨어를 위와같이 작성하였는데 사실 좀 더 간략하게 할 수 있는 법이 있지는 않을까 생각이 든다만
아직나의 학습으로 가능한 수준은 여기까지 이다. 좀 아쉽긴 하지만 나중에 회고해 보도록 하겠다.
이렇게 해서 개인과제를 모두 완성 하였다. 처음에는 할 수 있을까? 라는 생각이 앞섰지만 배운 강의 내용을 확인하며
작성하니 생각보다 수월하게 작성을 하였다.
그러나 온전히 내것으로 순수히 아무것도 없는 상태에서 이정도를 작성하기에는 아직 무리가 있다.
그러므로 그 숙련은 반복 학습을 통해 단련할 것이다.
추가적으로 회고해야 할 부분을 넣고 마치도록 하겠다.