(베이커리 이커머스 프로젝트 2일차)
오늘 한 일
- 어제 만든 DB 스키마를 보강하고 sequelize 모델을 만들었다. 깃험에 pull 완료.
⇒ ‘product’와 ‘cart’ 테이블의 관계에서 Many-to-Many를 굳이 적용해야 하나를 고민하다가 (mapping 테이블이 하나 더 만들어진다는 게 꺼려졌다) 이게 사실은 One-toMany 관계라는 것을 깨달았다. 기존의 ‘cart’ 테이블 자체가 ‘user’와 ‘product’를 Many-to-Many로 연결지어주는 매핑 테이블이었던 셈이다. ‘cart’라는 테이블명 때문에 더 헷갈렸던 것 같아서, ‘cart-item’이라고 이름을 바꿔주었다.
⇒ 참고로 user와 product 테이블이 Many-to-Many인데 중간에 cart_item이 매핑 테이블 역할을 하고 있고,
⇒ order와 product 테이블도 Many-to-Many인데 중간에 order-item이 매핑 테이블 역할을 하고 있는 형국이다.
- dotenv 모듈 도입, Sequelize의 사용자 비번 등을 환경 변수로 교체 완료. 깃헙에 pull 완료
- ‘상품’ CRUD 반절
아래는 수정 전과 수정 후 ERD:
로컬 ↔ 원격 pull/push 를 위한 명령어
origin이라는 이름의 원격(깃허브) 레파지토리의 ‘feature/mypage’ 브랜치에 내 로컬 ‘master’브랜치를 푸시하고 싶을 때:
git push origin master:feature/mypage
내 현재 브랜치로 원격 origin/develop 브랜치를 pull 해오기:
git pull origin develop
내일 할 일:
- ‘상품’ CRUD 작성 완료하기.
- 프론트 작성 시작 및 ejs 조사
- (옵션)nodemon 환경 세팅은 프론트를 만들 때 하자.
"scripts": { "start": "nodemon app.js" }, nodemon.json 설정파일 없이 그냥 이걸로 충분한가?
// nodemon.json 예시 { "watch": ["index.ts"], "ext": ".ts, .js", "ignore": [], "exec": "npx ts-node ./index.ts" }
- (옵션)테스트에 Joi 활용을 고민중. 일단은 API 완성이 먼저다.
파이참에서 파일 확장자 인식 못함 오류
파이참에서 파일 확장자 인식 못함 오류
발생 상황:
파이참 에디터가 갑자기 자바스크립트 코드를 코드로 인식하지 않는 문제가 발생했다. 예를 들어 줄바꿈 시 자동 들여쓰기나 바디 블록 인식이 전혀 안 된다. 코드 자동완성 기능도 없어졌다.
시도:
파이참 IDE 전체 껐다 켜보기 ⇒ 안됨.
파이참 Settings에서 이리저리 건드려 보았으나 잘 안됨.
원인:
파이참이 자바스크립트 확장자를 가진 파일을 text 파일 타입으로 인식하고 있었음.
해결:
- Settings > Editor > File Types > “Text”를 찾아 등록된 패턴 “*.js” 삭제
- Settings > Editor > File Types > “JavaScript”를 찾아 “*.js”패턴 추가
특별히 건드린 게 없는데 갑자기 왜 이렇게 설정되어 있던 건진 모르겠으나, 이것으로 해결 완료됨.
(참고: https://ahn3330.tistory.com/63)
async/await과 promise.then/catch 사용 비교
async/await과 promise.then/catch 사용 비교
async/await
을 사용하면await
가 대기를 처리해주기 때문에.then
이 거의 필요하지 않다. (=async
함수도promise
를 반환하는 건 마찬가지이므로.then
을 붙이는 게 가능하다.)
- 여기에 더하여
.catch
대신 일반try..catch
를 사용할 수 있다는 장점도 생긴다. (=.catch
를 쓰는 것보다try…catch
구문이 더 좋게 보인다는 거군.)
⇒ 결론: async/await
를 사용하면 promise.then/catch
가 거의 필요 없다.
async
를 사용하면서도 promise.then/catch
를 사용하는 경우 :
async
함수 내에서await
으로 발생한 ‘프라미스 거부 상태’를try…catch
로 처리해주기를 깜빡할 경우를 대비하여.
- 가장 바깥 스코프에서 비동기 처리가 필요할 때.
⇒
async
함수 바깥인 최상위 레벨 스코프에선 (당연한 얘기지만)await
을 사용할 수 없으므로 보통의 비동기 함수를 사용하게 되는데, 이 경우 거부된 프라미스를 처리할 수 있는 유일한 방법은 비동기 함수f()
에.catch()
를 추가하는 방법 뿐이다.
⇒ 결론: 1과 2의 경우 모두를 아울러서 관행처럼 (async
비동기 함수든 최상위 스코프에서 (어쩔 수 없이) 사용되는 일반 비동기 함수든) 끝에 .then/catch
를 추가해 최종 결과나 처리되지 못한 에러를 다룬다.
// .catch 처리 예시:
async function f() {
let response = await fetch('http://유효하지-않은-주소');
// try...catch로 처리하는 것을 잊음.
}
// f()는 거부 상태의 프라미스가 됩니다.
f().catch(alert); // TypeError: failed to fetch // (*)
(참고: https://ko.javascript.info/async-await#ref-1373)
(팁) 최상위 레벨에서 await 사용하기
:
async
익명 즉시 호출 함수로 코드를 감싸면 된다.(async () => { let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); ... })();
적용 - 에러 발생:
3계층 애플리케이션을 만들고 있는데, service나 repository 계층에서
- await으로 작성한 코드에서 프라미스 거부가 발생하거나,
// services/product.service.js ... updateProduct = async (product_id, ...) => { // product_id로 수정할 데이터가 있는지 검사. const findProduct = await this.productRepository.findProductById(product_id); if (!findProduct) { throw new Error("Product doesn't exist"); } ... }
- 직접 에러를 만들어 던질 수 있다.
// services/product.service.js ... updateProduct = async (product_id, ...) => { // product_id로 수정할 데이터가 있는지 검사. const findProduct = await this.productRepository.findProductById(product_id); if (!findProduct) { throw new Error("Product doesn't exist"); } ... }
어느 쪽이든 상위 계층인 controller에서 try…catch
문으로 받으면 된다!
적용 - 에러 처리:
catch(error)
에 들어오는 error
가 어느 계층에서 발생된 에러든(즉 몇 단계를 거쳐오든), 직접 던진 에러든 await
이 발생시킨 에러든 훌륭히 다 받는다.
// controllers/product.controller.js
updateProduct = async (req, res, next) => {
const { product_id } = req.params;
...
try {
const updated = await this.productService.updateProduct(product_id, ...);
return res.status(200).json({
message: '상품 수정이 완료되었습니다.',
});
} catch (error) {
console.log(error.message);
return res.status(500).json({
errorMessage: error.message,
});
}
};
- 이 전 계층인 product.service.js에서 1번 상황, 즉 await 부분에서 프라미스 거부가 발생한다면 프라미스가 발생시킨 에러 메세지가 errorMessage에 담기게 된다. 더 전 계층인 repository.js에서 에러가 발생한 경우가 바로 이 경우이겠다.
- (await 부분을 잘 통과하고서) 직접 만든 에러가 던져진 경우, 여기서 작성한 메세지 “Product doesn’t exist”가 errorMessage에 담기게 된다. 이 전 repository 계층에서 findByPk 쿼리로 찾은 결과가 null일 때(=아무것도 못찾은 경우)가 이 경우에 해당하게 된다.
괜히 코드를 지저분하게 쓰는 것도 아니고 다른 더 나은 대안이 있는데 어쩌다 보니 이런 식으로 쓰는 것도 아니라, 바로 이 방법이 정석이다. 라고 나름 결론을 내릴 수 있어 속이 시원하다.
Sequelize에 dotenv 적용시키기
Sequelize에 dotenv 적용시키기
Sequelize를 세팅하면서 만든 원래의 파일들에서 세 가지만 바꿔주면 된다.
- config/config.json 파일을 config/config.js 파일로 바꿔주기.
// config.json { "development": { "username": "root", "password": "12341234", "database": "db_development", "host": "some.database.rds.amazonaws.com", "dialect": "mysql" }, "test": { ... }, "production": { ... } }
// config.js require('dotenv').config(); const development = { username: process.env.MYSQL_AWS_USERNAME, password: process.env.MYSQL_AWS_PASSWORD, database: process.env.MYSQL_AWS_DATABASE, host: process.env.MYSQL_AWS_HOST, dialect: 'mysql', }; const test = { ... }; const production = { ... }; module.exports = { development, test, production };
- model/index.js > config 부분을 수정하기
// model/index.js ... const config = require(__dirname + '/../config/config.json')[env]; const config = require(__dirname + '/../config/config.js')[env];
- package.json파일과 같은 레벨에 .env 파일을 하나 만들고 필요한 환경 변수를 몽땅 적어 넣는다.
// .env PORT=7000 ... MYSQL_AWS_USERNAME=root MYSQL_AWS_PASSWORD=12341234 MYSQL_AWS_DATABASE=db_development MYSQL_AWS_HOST=some.database.rds.amazonaws.com
- .gitignore 파일에 .env를 추가한다.
(참고: https://velog.io/@hyunju-song/sequelize로-DB셋팅할-때-환경변수-파일-설정-및-사용하기)
고민중…
고민중…
DB에서 가져올 때부터 컬럼을 고르거나 정렬해서 가져오는 게 더 나을까?
-
findAll 전체 가져와서 필요한 컬럼 고르고 데이터 trim하기vs. 처음부터 몇 컬럼만 가져오기
-
findAll 전체 가져와서 정렬하기vs. 처음부터 order by로 가져오기
Sequelize findAll의 limit과 offset 옵션으로 진짜 페이지네이션이 가능할까?
여기 (빈약한) 참조: https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#limits-and-pagination
Limits and Pagination
The limit
and offset
options allow you to work with limiting / pagination:
// Fetch 10 instances/rows
Project.findAll({ limit: 10 });
// Skip 8 instances/rows
Project.findAll({ offset: 8 });
// Skip 5 instances and fetch the 5 after that
Project.findAll({ offset: 5, limit: 5 });
Usually these are used alongside the order
option.
Joi로 검사하기
튜터님 코드 뜯어보는 중…
찾은 코드 조각 및 용례:
// routes/comments.js
const {
commentCreateValidation,
commnetUpdateValidation,
} = require('../validations');
// routes/comments.js
...
} catch (err) {
if (err.isJoi) {
return res.status(422).json({ message: err.details[0].message });
}
res.status(500).json({ message: err.message });
}
// validations/index.js
const Joi = require('joi');
const signupValidation = Joi.object({
nickname: Joi.string().alphanum().not('').required(),
password: Joi.string().min(3).not('').required(),
confirm: Joi.equal(Joi.ref('password')).required().messages({
'any.only': '비밀번호가 일치하지 않습니다.',
}),
});
const postCreateValidation = Joi.object({ ... })
// ...
module.exports = {
signupValidation,
postCreateValidation,
...
};
Uploaded by N2T