백엔드 실습(13)

wltjd1688·2025년 3월 24일

풀사이클

목록 보기
40/74

주문 api의 회원인증을 추가 및 프로젝트 이후에 추가할 것들을 정리했다.
또한 프로젝트를 위한 추가와 수정을 할 예정이다.

1. 이전과 같이 주문 api에 jwt토큰을 확인하는 걸 추가해주었다.

2. 개별 도서 조회에 like표시 여부를 위해 추가해야함

3. 개별 도서 조회에서 로그인 상태가 아니라면 liked 제외

  • 로그인을 안했다면 JWT가 없으므로 JsonWebTokenError가 나오므로, liked를 제외하여 표시

  • 로그인을 했다면, 이전 그대로 표시

    const bookDetail = (req,res)=>{
        let book_id = req.params.id;
    
        let authorization = ensureAuthorization(req);
    
        let temp = `,(SELECT EXISTS (SELECT * FROM likes WHERE user_id=? AND liked_book_id=?)) AS liked`;
        let values = [book_id, authorization.id,book_id, book_id];
    
        if ( authorization instanceof jwt.TokenExpiredError){
            return res.status(StatusCodes.UNAUTHORIZED).json({
                "message": "로그인 세션이 만료되었습니다. 다시 로그인해주세요"
            })
        } else if (authorization instanceof jwt.JsonWebTokenError){
            temp = ``;
            values = [book_id, book_id];
        }
        let sql = `SELECT *,
            (SELECT count(*) FROM likes WHERE liked_book_id=?) AS likes${temp}
            FROM books 
            LEFT JOIN category
            ON books.category_id = category.id
            WHERE books.id=?;`
        db.query(sql, values, (err, results)=>{
            if (err){
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
    
            if(results[0])
                return res.status(StatusCodes.OK).json(results);
            else
                return res.status(StatusCodes.NOT_FOUND).end();
        });
    }
    

4. 좋아요 추가, 삭제 로직 response 좋아요 수

나중에 좋아요 추가랑 삭제를 통합하여 구현할 수 있지 않을까 하는 생각이 있음

5. 장바구니 도서 삭제, 주문 (상세) 내역 조회에 로그인 로직 추가

6. 전체 도서 조회 pagination 추가

SELECT * FROM Bookshop.books LIMIT 4 OFFSET 0;
SELECT count(*) FROM Bookshop.books

SELECT SQL_CALC_FOUND_ROWS * FROM Bookshop.books LIMIT 4 OFFSET 0;
SELECT found_rows();

2개는 같은 결과를 출력하지만, 밑에꺼가 Bookshop.books를 불러오면서 갯수를 세어두라고 명령한 것이기에, 첫번째와 달리 한번만 실행하는 장점이 있다.

전체 도서 조회 response body 구성 맞추기

프론트에서는 요청한 값을 저장해두지 않기 때문에, 현재 요청한 페이즈를 백엔드에서 담아줘야한다.
그래서 다음과 같이 outpu이라는 객체를 만들고, 쿼리를 한번 더 사용하여 책의 총 갯수를 보내주는 코드를 작성하였다.

아래의 result에서 "found_rows()"함수로 인해 값을 받아오기 때문에 output.pagenation.totalCount = results[0].found_rows(); 이런 형태로는 받아올 수 없다.
그래서 다음과 같이 수정해 주었다.
output.pagenation.totalCount = results[0]["found_rows()"];

    let output = {
        books: [],
        pagenation:{
            currentPage: parseInt(currentPage),
            totlaCount:0
        }
    }
    db.query(sql, values, (err, results)=>{
        if (err){
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }
        if(results.length){
            return output.books = results
        }
        return res.status(StatusCodes.BAD_REQUEST).end();
    });

    sql = "SELECT found_rows();"
    db.query(sql, values, (err, results)=>{
        if (err){
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }

        output.pagenation.totalCount = results[0]["found_rows()"];

        return res.status(StatusCodes.OK).json(output);
    });

response 포맷 확인하기
1. 다음과 같이 출력되는 포맷을 map함수를 통해 바꿔줄 수 있다.

    db.query(sql, values, (err, results)=>{
        if (err){
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end();
        }
        if(results.length){
            results.map(function(result) {
                result.pubDate = result.pub_date;
                delete results.pub_date;
            })
            return output.books = results
        } else 
            return res.status(StatusCodes.BAD_REQUEST).end();
    });

또는 불러올 때 sql문에 as를 사용하여 포맷 형식을 바꿔줄 수 있다.

SELECT id, title,... (pub_date AS pubDate), ... FROM books...

error status code 고려해보기

affectedRows를 통해 값이 올바르게 들어가거나 수정되었는지를 판단할 수 있다.

if(results.affectedRows)
  return res.status(StatusCodes.CREATED).json(results);
else
  return res.status(StatusCodes.BAD_REQUEST).end();

끝날때 까지 끝난 게 아니다 (feat. 코드 퀄리티)

[프로젝트 과제] 코드 퀄리티 올려보자

  • response 포맷 (snake>camel), status code ...
  • 데이터베이스 코드 -> 중복되는게 많다 -> 모듈화를 해야한다.
    • ex.UserController: USer(데이터 모듈) - CRUD를 다 빼서 저장해두기
      컨트롤러는 User모둘에서 꺼내올 수 있음
    • cf. DB 모둘: mysql => 몽구스, 시퀄라이즈등을 사용할 수 있다.

근데 라이브러리 쓰는건 안에 내부가 어떻게 돌아가는지 알고 사용하는게 좋다.

  • 패키지 구조

    1. Router: 경로(URL, URI)와 HTTP method로 요청에 따른 경로를 찾아주는 역할
    2. Controller: 길 매니저 - 요청을 환영!, 직접 일을 하진 않아요.
    3. Service: 직접 일을 하고 ex. 어떤 쿼리를 부를지,
      "비지니스 로직"
    4. Model: 데이터베이스와 소통 -> Query 집합
  • 예외처리(try/catch) 더 해줄 곳 없는지

  • 유효성 검사 추가

  • jwt (심화): access token이 만료되면, '로그인 연장?'
    *로그인 시 access token(30m), refresh token(24h)
    1) access token: 이건 현재 코드에 구현된거 ( 로그인한 사람이야 인증)
    2) refresh token: 로그인 연장 용도

  • 초초초 미니 프로젝트 ( 랜덤 데이터 , 외부 api를 활용해서 isbn 샘플 데이터들을 채워볼 수도 있겠다.

  • nodemon 모듈 : 변경사항이 있으면 자동으로 다시 실행해주는 라이브러리, 알아두면 좋음


랜덤 데이터 API 알아보기

  • 개요
    랜덤 데이터를 생성해주는 API (외부 API)를 기반으로 "가짜 사용자 정보 생성" API

  • 내용
    1) 랜덤 데이터 생성 API...? (가짜 사용자, 가짜 상품, 가짜 전화번호 등등)
    2) 가짜 사용자 정보를 생성하는 Express 웹/앱 API

  • 랜덤 데이터 생성 API

    • faker(faker.js): npm을 통해 다운받을 수 있으므로 이걸 채택함
    • mockaroo

faker 간단하게 사용해보기

npm에 있는 faker를 간단하게 이용해보자.
faker

const { faker } = require('@faker-js/faker');

console.log(faker.internet.username())
console.log(faker.internet.email())
console.log(faker.internet.password())

가짜 사용자 1명 만들어주는 API 만들어 보기

다음과 같이 간단하게 만들어 줄 수 있다.

// express 모듈
const express =require('express');
const { faker } = require('@faker-js/faker');
const app = express();

app.use(express.json());

app.listen(5555);

app.post('/fake/users',(req,res)=>{
    const users = {
        email: faker.internet.email(),
        password: faker.internet.password(),
        fullname: faker.internet.username(),
        contact: faker.phone.number()
    }
    res.status(200).json(users)
})

app.get('/',(req,res)=>{
    res.send('Hello World!');
})

원하는 사용자 수만큼 가짜 데이터 만들어주는 API로 수정해보기

app.get('/fake/users/',(req,res)=>{
    const {num }= req.query;
    let users= [];
    let i = 1;
    while(i <= num){
        users.push({
            email: faker.internet.email(),
            password: faker.internet.password(),
            fullname: faker.internet.username(),
            contact: faker.phone.number()
        })
        i++;
    }
    res.status(200).json(users)
})
profile
일단 해!!!!

0개의 댓글