백엔드 개발자(node.js)가 되는 과정

백엔드 기초 입문과 미니 프로젝트 02

soopy 2023. 5. 16. 21:33
728x90

프로젝트 목표

본인 소개 페이지와 하단에 코멘트를 남길 수 있는 개인 페이지를 만든다.

구현 내용

Pagination

  1. 한 페이지 당 보여질 코멘트의 개수를 지정하고 limit = 5와 같은 형태로 변수 지정한다.
  2. 페이지 수가 많을 것을 대비하여 n개 단위로만 페이지 번호가 보여지도록 한다.
    예를 들어 5개 단위로 페이지를 그룹지을 경우 최초 1 ~ 5페이지 번호만 보여지며
    6페이지로 이동할 경우 6 ~ 10 페이지 번호가 보여지게 한다.
    페이지번호 노출 범위에 대한 정보를 page_set = 5와 같은 형태로 변수 지정한다.
  3. Previous와 Next 버튼은 다음 페이지 그룹이 노출되도록 하는 기능이다.
    가령 현재 페이지가 1 ~ 5페이지 내 있는 경우 Next 버튼을 클릭하면 6페이지로 이동하고,
    6 ~ 10페이지 내 속한 경우 Next 버튼을 클릭 시 11 페이지로 이동한다.
  4. 현재 페이지에 해당하는 페이지 번호는 텍스트 색깔을 달리 한다.
  5. Previous와 Next 버튼이 불필요한 경우 숨긴다.

백엔드 구현

@app.route("/pmy/comments", methods=["GET"])
def get_comments():
    """코멘트를 가져옵니다."""
    page = int(request.args.get("page"))
    limit = int(request.args.get("limit"))
    limit = limit if limit <= 20 else 20

    count = db.comments.count_documents({})

    page_set = 5
    page_group_num = (page - 1) // page_set
    start_page = page_group_num * page_set + 1
    end_page = (page_group_num + 1) * page_set
    # MongoDB에서 코멘트 데이터 가져오기
    comments = list(
        db.comments.find({}, {"_id": False})
        .skip((page - 1) * limit)
        .limit(limit)
        .sort("upload_time", DESCENDING)
        
    )

    return jsonify(
        {
            "count": count,
            "start_page": start_page,
            "end_page": end_page,
            "page_set": page_set,
            "comments": comments,
        }
    )

 

1. 현재 페이지에 따른 페이지네이션 구현하기

만약 코멘트의 총 개수가 100개이며, 한 페이지 당 5개씩 보여진다고 했을 때 1 ~ 20페이지의 범위를 갖게 된다.
하지만 페이지 번호를 1에서 20까지 전부 노출시키지 않고 1 ~ 5, 6 ~ 10, 11 ~ 15...이렇게 페이지 다섯개씩 한 묶음으로 보여준다면 좀 더 깔끔하게 페이지네이션을 구현할 수 있다.
예를 들어 < 1 2 3 4 5 6 7....> 이렇게 나열하지 않고 처음에는 <1 2 3 4 5>만 보여줬다가 ">"버튼을 클릭하면 <6 7 8 9 10> 페이지가 보여지도록 하는 것이다.

 

먼저 아래 간단한 공식을 살펴보자

page_set = 5 # 페이지 묶음 단위
page_group_num = (page - 1) // page_set
start_page = page_group_num * page_set + 1
end_page = (page_group_num + 1) * page_set
  • page_group_num에 적용된 공식을 보면 page 값이 1 ~ 5일 때는 0, 6 ~ 10일 때는 1, 11 ~ 15일 때는 2가 구해지는 것을 확인할 수 있다. 이를 통해 각 page번호마다 그룹 번호를 부여할 수 있게 된다.
  • 각 페이지번호의 그룹 번호를 알 수 있다면 page_set과의 연산을 통해 해당 그룹의 최소, 최대 페이지 번호를 구할 수 있게 된다.

정리하자면 만약 현재 page가 8페이지라면 html상에서 페이지네이션은 < 6 7 8 9 10 >으로 표현되어야 할텐데 이를 구현하기 위해 6과 10을 구했다고 볼 수 있다.

2. MongoDB에서 필요한 만큼 데이터 가져오기

comments = list(
    db.comments.find({}, {"_id": False})
    .skip((page - 1) * limit)
    .limit(limit)
    .sort("upload_time", DESCENDING)
)
  • mongoDB의 skip과 limit 메소드를 통해 데이터 인덱싱이 가능하다. 현재 보여줄 page가 8번이라고 가정한다면 8페이지의 첫번째 코멘트부터 limit만큼만 가져오면 된다.
  • skip 메소드는 이름 그대로 n번째 데이터까지는 건너뜀을 의미한다.
  • limit 메소드는 수집 시작 부분부터 n개까지만 수집함을 의미한다.

3. 페이지네이션 구현 백엔드에서 받은 response로 아래와 같이 프론트를 구현했다.

// pagination
$('.pagination').empty()
let total_page = Math.ceil(count / limit);

// previous button
if (page > page_set) {
  let previous = `<li class="page-item">
                    <a class="page-link" href="/pmy?page=${start_page - page_set}&limit=${limit}" aria-label="Previous">
                      <span aria-hidden="true">&laquo;</span>
                    </a>
                  </li>`
  $('.pagination').append(previous)
}

// pages
let page_list;
for (let i = start_page; i <= end_page; i++) {
  let color 
  let url = `/pmy?page=${i}&limit=${limit}`
  if (i > total_page) {
    break
  } else {
    if (page === i) {
      color = 'red'
    }
    page_list = `<li class="page-item"><a class="page-link" style="color: ${color};"href="${url}">${i}</a></li>`
  };
  $('.pagination').append(page_list)
}

// next button
if ((page <= total_page - (total_page % page_set)) && (total_page > page_set)) {
  let next = `<li class="page-item">
                <a class="page-link" href="/pmy?page=${start_page + page_set}&limit=${limit}" aria-label="Next">
                  <span aria-hidden="true">&raquo;</span>
                </a>
              </li>`
  $('.pagination').append(next)
}

 

먼저 Previous 버튼 구현을 보자

// previous button
if (page > page_set) {
  let previous = `<li class="page-item">
                    <a class="page-link" href="/pmy?page=${start_page - page_set}&limit=${limit}" aria-label="Previous">
                      <span aria-hidden="true">&laquo;</span>
                    </a>
                  </li>`
  $('.pagination').append(previous)
}
  • Previous 버튼은 이전 페이지 그룹으로 넘어가도록 하는 기능이다. 페이지 그룹에 대해 다시 한 번 설명하자면 (1 ~ 5) 페이지를 그룹 0, (6 ~ 10)페이지를 그룹 1로 정의함을 뜻하며 만약 현재 7페이지에 머물고 있다면 Previous 버튼 클릭 시 1페이지로 이동해야 하며, 13페이지에 머물고 있다면 6페이지로 이동해야 한다.
  • 추가적으로 그룹 0에 해당하는 (1 ~ 5) 페이지가 노출될 경우 Previous 버튼은 필요없기에 숨겨야 한다. 이를 if 문으로 적용했다.

다음으로 각 page 번호 구현을 보자

// pages
let page_list;
for (let i = start_page; i <= end_page; i++) {
  let color 
  let url = `/pmy?page=${i}&limit=${limit}`
  if (i > total_page) {
    break
  } else {
    if (page === i) {
      color = 'red'
    }
    page_list = `<li class="page-item"><a class="page-link" style="color: ${color};"href="${url}">${i}</a></li>`
  };
  $('.pagination').append(page_list)
}
  • 리스폰 받았던 start_page와 end_page 정보를 통해 for문의 범위를 정해주고 정해진 범위가 곧 프론트에 페이지네이션 번호로 출력된다. 이렇게 for문을 적용하는 이유는 각 페이지 번호마다 해당하는 url 링크를 적용해야 하기 때문이다.
  • color 변수를 통해 현재 머물고 있는 페이지 번호는 빨간색으로 표시되도록 했다.
  • 마지막으로 start와 end 페이지 번호는 현재 머물고 있는 페이지가 속한 페이지 그룹의 시작과 끝 번호를 출력하는 단순한 계산이므로 마지막 페이지 그룹에 대한 대책이 필요하다.
    가령 8페이지가 마지막 페이지라고 한다면 6 ~ 8 페이지 번호가 프론트에 노출되어야 할텐데 대책이 없다면 6 ~ 10 페이지 번호가 노출될 것이다. 이를 방지하기 위해 for문에서 i가 마지막 페이지보다 클 경우 break하도록 적용했다.

마지막으로 Next 버튼 구현을 보자

// next button
if ((page <= total_page - (total_page % page_set)) && (total_page > page_set)) {
  let next = `<li class="page-item">
                <a class="page-link" href="/pmy?page=${start_page + page_set}&limit=${limit}" aria-label="Next">
                  <span aria-hidden="true">&raquo;</span>
                </a>
              </li>`
  $('.pagination').append(next)
}
  • next 버튼이 존재하지 않아야 되는 상황이 두 가지 있다.
  • 첫번째로 코멘트량이 많지 않아 총 5페이지 분량을 넘지 못한다면 next 버튼은 필요없다. 두번째로 마지막 페이지 그룹에 도달한다면 next 버튼은 필요없다.
  • 위 if문은 위 두가지 조건을 만족하지 않는다면 버튼이 생성되도록 도와준다. 참고로 모듈러 연산(%)은 나머지 값을 출력한다.
728x90
728x90