[Week7] Node.js 기반의 REST API 구현(8)

Younha Lee·2026년 2월 24일

TIL

목록 보기
33/60
post-thumbnail

좋아요 DB 설계


fk를 두 개 갖고있는 좋아요 테이블을 만들었어요.

좋아요 추가 및 삭제 구현

원래라면 헤더에서 추출한 jwt로 user_id를 req 객체에 넣어줘야하지만, jwt를 req 객체에 넣는 기능을 추후에 개발하고 일단은 body로 받기로 했어요.

export const addLike = (req, res) => {
    const id = req.params.id;
    const { user_id } = req.body;
    const sql = `INSERT INTO likes (user_id, liked_book_id)
    VALUES (?, ?)`;
    const values = [user_id, id];
    conn.query(sql, values, (err, results) => {
        if (err) return res.status(statusCode.INTERNAL_SERVER_ERROR).json({message: err.message});
        return res.status(statusCode.CREATED).json(results);
    });
};

export const deleteLike = (req, res) => {
    const id = req.params.id;
    const { user_id } = req.body;
    const sql = `DELETE FROM likes WHERE user_id = ? AND liked_book_id = ?`;
    const values = [user_id, id];
    conn.query(sql, values, (err, results) => {
        if (err) return res.status(statusCode.INTERNAL_SERVER_ERROR).json({message: err.message});
        return res.status(statusCode.CREATED).json(results);
    });
}

SQL을 사용해서 구현했어요.

count(), AS, 서브쿼리의 등장 1탄

count()

count()를 사용하면 테이블의 행 수를 셀 수 있습니다. 개수가 필요한 경우나 존재하는지(0과 1로 구분 가능)를 확인할 수 있습니다.

SELECT count(*) FROM table;

AS

테이블 칼럼명이 마음에 안 드는 경우도 이썽요. 그렇다고 마음대로 테이블의 칼럼을 바꾸면 곤란한 상황이 올 수 있습니다. 그럴 때 사용하는 것이 AS를 통해서 별명(다른 이름)을 만들어서 사용하는 것입니다.

SELECT 기존컬럼명 AS 원하는컬럼명 FROM table;

SELECT *, 
(SELECT COUNT(*) FROM likes WHERE liked_book_id = books.id) AS likes 
FROM books;


와 같이 하니 좋아요의 개수까지 넣어서 반환되었어요

서브 쿼리

서브 쿼리란 쿼리 안에 쿼리를 의미합니다. 쉽게 말해 기존 쿼리가 있고 그 안에서 또 다른 쿼리를 사용하는 것입니다. 다양한 예시가 있지만 SELECT문으로 예시를 들 수 있어요..

SELECT *, (SELECT 필요한컬럼 FROM table2) FROM table1;

서브쿼리(Subquery) 서브쿼리(subquery)란 다른 쿼리 내부에 포함되어 있는 SELETE 문을 의미한다.
서브쿼리를 포함하고 있는 쿼리를 외부쿼리(outer query)라고 부르며, 서브쿼리는 내부쿼리(inner query)라고 한다.

도서 조회하면서 좋아요 수 반환하기

export const findBooks = (req, res) => {
    const { categoryId, isNew, limit, currentPage } = req.query;
    let sql = 'SELECT *, (SELECT count(*) FROM likes WHERE books.id=liked_book_id) AS likes FROM books';
    const offset = limit * (currentPage - 1);
    let values = [];
    if (categoryId && isNew) {
        sql += ' WHERE category_id = ? AND pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()';
        values.push(categoryId);
    }
    else if (categoryId) {
        sql += ' WHERE category_id = ?';
        values.push(categoryId);
    }
    else if (isNew) {
        sql += ' WHERE pub_date BETWEEN DATE_SUB(NOW(), INTERVAL 1 MONTH) AND NOW()';
    }
    sql += ' LIMIT ? OFFSET ?';
    values.push(parseInt(limit || 3), offset);

    db.query(sql, values, (err, results) => {
        if (err) return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({message: err.message});
        return res.status(StatusCodes.OK).json(results);
    });
};

변경점은 서브쿼리를 포함한 sql로 변경했다는 점이에요. 응답도 잘 돌아왔어요.

[
    {
        "id": 3,
        "title": "백설공주들",
        "img": 3,
        "form": "종이책",
        "isbn": "2",
        "pages": 100,
        "summary": "사과..",
        "detail": "빨간 사과..",
        "author": "김사과",
        "contents": "목차입니다.",
        "price": 20000,
        "pub_date": "2026-02-01",
        "category_id": 0,
        "likes": 3
    },
  ]

좋아요 여부 확인하기

개별 도서 조회시 사용자가 좋아요 했는지 여부를 추가해야해요

단어 그대로 값이 존재하는지의 여부를 확인하는 함수입니다. count()로도 확인할 수 있지만 직관적으로 알아보기에는 EXISTS가 좋아 보입니다. 존재 여부는 0은 존재하지 않고, 1은 존재하는 것을 의미합니다.

SELECT EXISTS (SELECT * FROM table WHERE id = id);

id가 덮여씌여짐

카테고리와 책 모두 pk 이름이 id라 덮어씌워지는 문제가 있었어요.
카테고리 테이블의 pk 이름을 category_id 로 변경했어요.

export const findBookById = (req, res) => {
    const { user_id } = req.body;
    const book_id = req.params.id;
    const sql = `SELECT *,
        (SELECT COUNT(*) FROM likes WHERE liked_book_id = books.id) AS likes,
        (SELECT EXISTS(SELECT * FROM likes WHERE user_id = ? AND liked_book_id=?)) AS liked
        FROM books
        LEFT JOIN category
        ON books.category_id = category.category_id
        WHERE books.id = ?;`
    const values = [user_id, book_id, book_id];
    db.query(sql, values, (err, results) => {
        if (err) return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({message: err.message});
        if (results.length === 0) return res.status(StatusCodes.NOT_FOUND).end();
        return res.status(StatusCodes.OK).json(results[0]);
    });
};
{
    "id": 1,
    "title": "어린왕자들",
    "img": 2,
    "category_id": 1,
    "form": "종이책",
    "isbn": "0",
    "pages": 100,
    "summary": "어리다..",
    "detail": "많이 어리다..",
    "author": "김어림",
    "contents": "목차입니다.",
    "price": 20000,
    "pub_date": "2019-01-01",
    "category_name": "사회",
    "likes": 3,
    "liked": 1
}

그리하여 도서 상세 정보 조회시 이러한 응답을 가져오게 됐어요.

현재까지의 DB ERD에요.

profile
할 땐 하고 놀 땐 노는 일일놀놀입니다.

0개의 댓글