회원인증 모듈화
- ensureAuth가 파일마다 반복된다. ➡️ 회원 인증 기능을 모듈화할 필요성을 느꼈다.
- auth.js 파일을 따로 만들어 모듈 exports 해준 뒤 모듈이 필요한 곳에서 호출해 쓴다.
const jwt = require('jsonwebtoken');
const dotenv = require('dotenv');
dotenv.config();
const ensureAuth = (req, res)=>{
try {
let receivedJwt= req.headers["authorization"];
console.log("received jwt: ", receivedJwt);
if(receivedJwt){
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
return decodedJwt;
}else{
// return receivedJwt; // undefined
throw new ReferenceError("jwt must be provided");
}
} catch (err) {
console.log(err.name);
console.log(err.message);
return err;
}
};
module.exports = ensureAuth;
auth.js
const ensureAuth = require('../auth'); // 인증 모듈
const jwt = require('jsonwebtoken');
// 주문하기
const order = async (req, res)=>{
const conn = await mysql.createConnection({
host: '127.0.0.1',
port: '3307',
user: 'root',
password: 'root',
database: 'Bookshop',
dateStrings: true, // 날짜 형식대로 표기
});
// 인증 복호화
let authorization = ensureAuth(req, res);
// instanceof : 객체가 어떤 클래스의 인스턴스인지 알아내기 위해 사용
if(authorization instanceof jwt.TokenExpiredError){
return res.status(StatusCodes.UNAUTHORIZED).json({
"message" : "로그인 세션이 만료되었습니다. 다시 로그인하세요."
});
}else if(authorization instanceof jwt.JsonWebTokenError){
return res.status(StatusCodes.BAD_REQUEST).json({
"message" : "잘못된 토큰입니다."
});
}
사용하는 곳 - OrderController
내 장바구니 조회
- 현재는 장바구니에서 선택한 주문 예상 상품 조회만 구현되어 있다.
- user_id가 있을 때 내 장바구니 목록 전체를 조회하는 기능이 추가로 필요하다.
- 선택한 장바구니 조회에서 분기 처리로 구현 가능
➡️ selected 유무로 분기 처리
// 장바구니 보기
let sql = `SELECT cartItems.id, book_id, title, summary, quantity, price
FROM cartItems
LEFT JOIN books ON books.id = cartItems.book_id
WHERE user_id = ?`;
let values = [authorization.id];
if(selected){ // 주문서 작성 시 선택한 장바구니 목록 조회
sql += ` AND cartItems.id IN (?)`;
values.push(selected);
}
// SELECT 쿼리문
conn.query(sql, values,
※ req.body 안보내주면 에러 남 ➡️ 빈 객체를 대체해서 구조분해 안전하게 작동하도록 한다.
const {selected} = req.body || {};
개별 도서 조회 좋아요 여부에 jwt 확인
- 분기처리필요
- 로그인 상태가 아니면 ➡️ sql문에서 liked 빼고 보내줌
로그인 상태면 ➡️ sql문에 liked 추가
- 로그인 상태가 아니면 ➡️ sql문에서 liked 빼고 보내줌
- 로그인 상태가 아닐 시 JsonWebTokenError 에러를 반환해주고 있었다.
- authorization이 없으니 토큰을 비교해서 잘못된 토큰이라고 말해주고 있는 것
➡️authorization이 없는 것, 즉 로그인 상태가 아닌 것과 잘못된 토큰을 보내주고 있는 것은 엄연히 다르므로
다른 에러 처리를 통해 구분 필요 - auth.js에서 received jwt: undefined 일 때 ReferenceError 던져주기
const ensureAuth = (req, res)=>{
try {
let receivedJwt= req.headers["authorization"];
console.log("received jwt: ", receivedJwt);
if(receivedJwt){
let decodedJwt = jwt.verify(receivedJwt, process.env.PRIVATE_KEY);
console.log(decodedJwt);
return decodedJwt;
}else{
// return receivedJwt; // undefined
throw new ReferenceError("jwt must be provided");
}
} catch (err) {
console.log(err.name);
console.log(err.message);
return err;
}
};
장바구니 도서 삭제, 주문 상세 내역 조회 jwt 인증 구현
- 로그인 상태여야 하는 기능들에 jwt 인증 기능을 추가한다.
➡️주문, 장바구니, 도서, 좋아요 등
전체 도서 조회 pagination 추가
- pagination을 계속 유지하려면 서버에서 클라이언트로 현재페이지와 총 도서 수를 보내줘야 한다.
➡️json에 넣어 보내준다.

- 총 도서 수 즉, 총 데이터(row) 수를 sql문을 통해 저장 및 조회할 수 있다.
- sql_calc_found_rows : 총 row 수 저장
- SELECT found_rows() : 저장한 총 row 수 가져오기
-- 총 row 수 저장 : sql_calc_found_rows
SELECT sql_calc_found_rows *
FROM books LIMIT 4 OFFSET 8;
-- 저장한 총 row 수 가져오기
SELECT found_rows();
- 코드에 sql문 수정
// SQL_CALC_FOUND_ROWS : 총 ROW 수 저장
let sql = `SELECT SQL_CALC_FOUND_ROWS *,
(SELECT count(1)
FROM likes
WHERE liked_book_id = books.id) AS likes
FROM books`;
let values = [];
//..(중략)..
// SELECT found_rows() : 저장된 총 ROW수 조회
sql = ` SELECT found_rows()`;
// sql = ` SELECT found_rows() AS foundRows`;
conn.query(sql,
(err, results) => {
if(err){
console.log(err)
return res.status(StatusCodes.BAD_REQUEST).end()
}
- allbooksRes 객체 생성해서 pagination에 필요한 currentPage와 totalBooks를 구해서 넣기
- currentPage : url에서 넘겨준 값 parseInt()해서 넣어줌
- totalBooks : found_row() 값
// SELECT found_rows() : 저장된 총 ROW수 조회
sql = ` SELECT found_rows()`;
// sql = ` SELECT found_rows() AS foundRows`;
conn.query(sql,
(err, results) => {
if(err){
console.log(err)
return res.status(StatusCodes.BAD_REQUEST).end()
}
// pagination 객체 생성해 넣기
let pagination = {};
pagination.currentPage = parseInt(currentPage);
pagination.totalCount = results[0]["found_rows()"];
allBooksRes.pagination = pagination;
return res.status(StatusCodes.OK).json(allBooksRes);
}
response 포맷 통일 (카멜 vs 스네이크)
- 전체 도서 조회에서 pub_date ➡️pubDate 변경
if(results.length){
// pub_date => pubDate 변경
results.map((result)=> {
result.pubDate = result.pub_date;
delete result.pub_date;
});
allBooksRes.books = results;
} else{
return res.status(StatusCodes.NOT_FOUND).end();
}
주문목록조회
- 회원id별로 조회 필요
else{
// 주문내역 페이지에서 보여줄 컬럼 셋팅 위해 orders와 delivery 조인
let sql = `SELECT orders.id, created_at, address, receiver, contact,
rep_book_title, total_quantity, total_price
FROM orders LEFT JOIN delivery
ON orders.delivery_id = delivery.id
WHERE user_id = ?`;
let [rows, fields] = await conn.query(sql, authorization.id);
return res.status(StatusCodes.OK).json(rows);
}
코드 퀄리티 높이기 위해 보완할 항목
- response 포맷 통일(snake>camel), status code...
- 데이터베이스 중복 코드 -> 모듈화
ex. UserController => User (데이터 모듈=Model) - CRUD
cf. DB 모듈 : mysql (자바로 치면 JDBC를 직접 해본 것) => 몽구스, 시퀄라이즈 - 패키지 구조
1) Router : 경로(URI, URL)와 HTTP method로 요청에 따른 경로를 찾아주는 역할
2) Controller : 길 매니저 - 요청을 환영!, 직접 일을 하진 않아요. ex. 어떤 서비스를 부를지
3) Service : 직접 일을 한다. ex. 어떤 쿼리를 부를지
"비즈니스 로직"
4) Model : 데이터베이스와 소통 -> 쿼리 집합 - 예외처리 (try/catch) 더 해줄 곳 없는지
- 유효성 검사 추가
- jwt(심화) : access token이 만료되면, '로그인 연장?'
- 로그인 시 access token(30m), refresh token(24h)
1) access token : 로그인 인증
2) refresh token : 로그인 연장하는 용도
- 로그인 시 access token(30m), refresh token(24h)
- 랜덤 데이터 (외부) API 활용해서 isbn 샘플 데이터들을 채워볼 수 있다.
- 좋아요 수 카운팅
패키지 구조를 나눠보고 model로 쿼리 집합을 빼주는 작업을 해보고 싶다. 더불어 typeORM을 적용해보고, 몽구스, 시퀄라이즈에 대해서도 따로 공부해봐야겠다. 유효성검사 까먹기 전에 express-validator 적용해보면서 복습해봐야겠다.
'프로젝트 > 도서구매사이트' 카테고리의 다른 글
| 1029 headers의 Authorization으로 jwt 인증 구현, try~catch 예외처리 (0) | 2025.10.29 |
|---|---|
| 1028 conn.query()의 비동기 처리, query 와 execute 차이, 주문 완료한 아이템 장바구니에서 삭제 (0) | 2025.10.28 |
| 1027 비동기 처리 방식 종류 - promise, async/await (0) | 2025.10.26 |
| 1024 주문 API 구현 - insertId 활용, 다중 인서트 구현 (0) | 2025.10.23 |
| 1023 장바구니 API 구현 - 선택한 장바구니 목록 조회 IN [배열] 적용 (0) | 2025.10.22 |