본문 바로가기

내일배움캠프_개발일지/내일배움캠프_미니프로젝트

팀 미니 프로젝트 "팀 소개 페이지 만들기" 결과물 정리.

이 문서에서는 고용노동부와 스파르타 코딩클럽이 함께 주최하는 내일배움캠프의 Node.js 트랙에서 막 코딩 공부를 시작한 

A반 8조의 5명이 함께 만든 '팀 소개 프로젝트' 에 대한 결과물을 설명하겠습니다.

 

<순서>

0. 소개

   - 팀원 소개

   - 당초의 목적과 마음가짐.

1. 프로젝트 소개 및 리뷰 

   - 프로젝트의 방향성, 컨셉

   - 팀의 목표

   - 레이아웃

2. 중요 기능 소개

   - 코드를 보면서 리뷰

   - 구체적으로 어떤 API 기능들이 구현되었는가

3. 종합

   - 당초의 목적에 부합한 프로젝트가 만들어졌는가?

   - 느낀 점, 그리고무엇이 어려웠나

   - 무엇을 해결하였고, 무엇을 해결하지 못했는가.

 

 

 

 

0. 팀원 소개.

안녕하십니까, Node.js 트랙 교육 과정 1주차를 수료 중인 A반 8조 입니다.

저희 팀은 다음과 같은 멤버로 구성되어 있습니다.

   - 김민수(팀장)

   - 김택환

   - 이재관

   - 정성욱

   - 표정훈

 

백엔드에 한해서는 모두가 비전공자이며, 그만큼 프로젝트를 설개하는데에 있어서는 초반에 어려움을 겪었습니다.

주로 방향성을 어떻게 잡아야 하는지, 어떻게 시작해야 하는지, 그리고 무엇보다, 분업을 어떻게 해야 하는지.

프로젝트를 만들어 본 경험이 없으니, API 별로 역할을 나눠야 할지, html 별로 역할을 나눠야 할지, 아니면 자바스크립트 기능별로 역할을 나눠야 할지 처음에는 갈피를 잡지 못했습니다.

하지만 그럴 수록 더 적극적으로 팀 회의에 모두가 참여하였고, 기본적인 프로젝트의 방향성과 html 의 레이아웃부터 시작해서 조금씩 기틀을 잡아가기 시작하였습니다.

이번 미니 프로젝트의 의의는 '프로젝트 협업을 체험하는 과정에서, 기존에 웹개발 종합반에서 배웠던 기능들을 응용해 보는 것' 에 있었습니다. 따라서 저희는 각자가 구현한 기능을 얼마나 조화롭게 하나로 묶어낼 수 있는지에 중점을 두었습니다.

 

 

 

 

1. 프로젝트 소개 및 리뷰

우선 저희 프로젝트의 방향성과 컨셉에 대해서 설명해 드리겠습니다.

보시는 사진은 저희가 제작한 웹 페이지에 접속하시면 가장 먼저 보시게 될 화면입니다.

주소는 다음과 같습니다. http://studynodejs.com/

 

가장 맨 위에 적혀있는 글귀대로, 저희 A반 8조의 팀명은 '잔고 8조' 입니다.

이는, 비록 지금은 막 백엔드 공부를 시작한 입문자일 뿐이지만, 언젠가 일취월장하여 큰 성공을 거두고 큰 돈을 벌겠다는 다짐을 나타냅니다.

따라서 저희는 페이지에 저희가 어떠한 각오와 포부를 지니고 있는지, 당장에 어떤 목표를 지니고 있는지, 그리고 그 목표를 이루기 위해 저희 팀원끼리 어떠한 약속을 맺었는지를 보여드리고 있습니다.

 

다음으로, 처음 구성했던 레이아웃을 보여드리겠습니다.

 

처음에는 이렇게 대략적인 와이어 레이아웃을 구성한 뒤, 조금씩 조금씩 채워나가는 방향으로 페이지를 제작하였습니다.

위는 대문 홈페이지, 아래는 팀원 각자를 소개하는 프라이빗 페이지 입니다.

 

그리하여 만들어진 홈페이지는 다음과 같습니다.

 

1) 첫 화면



 

 

2) 각 팀원의 이미지 카드를 클릭할 시 보여주는 각자의 개인 소개 페이지들

 

 

3) 각 팀원들의 개인 소개 페이지 하단에 위치한 방명록

 

4) 메인(home) 페이지 하단에 방명록과 게시글



 

 

 

 

2. 중요 기능 소개

1) home 페이지에서 팀 정보를 제공

<div class="col-6">
    <div class="whatIsTeam">
        <h2>잔고 8조는 어떠한 팀인가</h2>
        <p>
            내일배움캠프 node.js 트랙을 시작으로 프로그래밍 개발자로서의 인생을 살아가며, 언젠가는 통장의 잔고를 꼭 8조로 만들겠다는 의지를 담은 팀명입니다. <br>
            백엔드 개발자로서 나아가는 첫 한 걸음. 이 변화가 지금 이 순간에는 티끌 만도 못한 것일 지라도,
            언젠가는 찬란한 나비 효과가 되어 위대한 꿈이 될 수 있을 것입니다.
        </p>
    </div>
    <div class="teamsPur">
        <h2>우리들의 목표</h2>
        <p>
            도중에 포기하지 않고 끝까지 수료 해내기. <br/> node.js 환경에서 기본적인 백엔드를 구축할 수 있게 되기.
            <br/> 내배캠을 무사히 수료했을 때, DB로부터 데이터를 가져와 <br/> 소비자의 니즈에 맞춰서 적절한 서비스를 제공해 줄 수 있는 흐름을 파악하게 됨.
        </p>
    </div>
</div>

 

2) 각 맴버의 사진, 정보 등을 제공.

<div class="card" onclick="location.href='/minsoo';">
    <div class="card_img" style="background-image: url('../static/img/minsoo.jpeg')"></div>
    <div class="card-body">
        <p class="card-text">
            이름 : 김민수<br/>
            취미 : 유튜브 감상<br/>
            MBTI : ???<br/>
            TMI : 적성검사 무조건 공무원<br/>
        </p>
    </div>
</div>

바로 home 페이지의 카드 부분.

 

 

3) 팀원 각자의 소개 페이지에서 블로그 링크와 개인에 대한 정보를 제공.

 

4) API 기능 

가장 핵심적이며, 기존에 웹개발 종합반에서 학습한 flask, mongoDB, Ajax 통신을 적극적으로 활용, 응용할 수 있는 부분입니다.

우선 기본적인 프로젝트 틀은 위와 같습니다.

사실 현업 기준으로는, 하나의 템플릿을 두고 어떤 멤버의 카드를 클릭하느냐에 따라 데이터 베이스에서 값만 다르게 받아와서

템플릿으로 뿌려주는 것이 가장 이상적인 작업임을 알고 있습니다.

허나 이번 미니 프로젝트에서 가장 중요한 것은 '웹개발 종합반에서 배운 기본 지식들을 어떻게 협업에서 잘 살릴 수 있는가' 라고 저희는 판단하였습니다. 따라서 개개인 모두가 기본적인 API 기능 개발을 경험해 보는 것이 좋다고 판단한 바, 복잡하긴 하나 각자 개인 주소를 만들어서 합치도록 하였습니다.

또한, 해당 주소의 접속 시 render_template() 와 랜더링 시 바로 시동되는 get 구문을 따로 작업하였습니다.

이는 비효율적이긴 하나, 기존에 웹개발 종합반에서는 알기 쉽고 구현하기 편한 방식으로 교육을 받은 바 미니 프로젝트에서도 이와 같은 방식으로 코드를 구현하게 되었습니다.

현재 해당 결과물 정리본을 작성하고 있는 시점은 11월 18일 새벽 입니다. get 을 합쳐야 한다는 피드백을 17일 오후에 받았기에,

정리본을 준비하는 시점에서는 아직 restful api 정돈이 진행 중인 상황입니다.

 

 

현 시점에서 저희 8조가 구현한 API 는 사실상 방명록 기능에 모두 몰려 있습니다.

처음 퍼스널 주소에 접속했을 때 ajax get 으로 DB에 저장되어 있는 방명록 데이터를 가져와 카드 형태로 뿌려줍니다. (read)

상단에는 이름과 내용을 입력하여 방명록을 DB로 insert 시켜주는 기능이 존재하며, insert 가 성공적으로 구동되었을 시 클라이언트는 새로 고침을 시행하여 새롭게 get 으로 반영된 카드들을 보여줍니다.

마지막으로, 각 방명록의 우측 하단에 구비된 '삭제' 버튼을 누름으로서, 해당 레코드의 고유 id 값을 백엔드 측으로 POST 하여 

'나는 정확히 이 방명록을 삭제하고 싶다' 라고 지정해 줍니다.

 

 

5) get API

$(document).ready(function(){
    show_comment()
});

function show_comment(){
    $.ajax({
        type: "GET",
        url: "/minsoo/getList",
        data: {},
        success: function (response) {
            let visitors_list = response['result']

                for(let i=0; i<visitors_list.length; i++) {
                    let name = visitors_list[i]['name']
                    let comment = visitors_list[i]['comment']
                    let count = visitors_list[i]['num']

                    let temp = `
                                <div class="card" style="margin-bottom: 20px;">
                                    <div class="card-body" id="${count}">
                                        <blockquote class="blockquote mb-0" style="float: left;">
                                            <p>${comment}</p>
                                            <footer class="blockquote-footer">${name}</footer>
                                        </blockquote>
                                        <div style="float:right; margin-top:10px;">
                                            <button type="button" class="btn btn-danger" onclick="delete_msg(${count})">삭제</button>
                                        </div>
                                    </div>
                                </div>`
                    $('#comment-list').append(temp);
                }
        }
    })
}

해당 코드는 '김민수' 의 개인 소개 페이지에 처음 접속했을 때 일어나는 JS 구문입니다.

html이 로드된 직후에

$(document).ready(function(){
    show_comment()
});

라는 함수를 실행시켜 주고 있습니다. 해당 함수는 flask 에 ajax-get 으로 요청하여 방명록을 받아오고, 이를 html 에 뿌리고 있습니다.

 

# 개인 소개 페이지로 넘어갈 때 get 요청으로 방명록 list 를 제공.
@app.route("/minsoo/getList", methods=["GET"])
def intro_get():
    visitors_list = list(db.minsoo.find({}, {'_id': False}))

    return jsonify({'msg': 'GET 연결 완료!', 'result': visitors_list})

위 코드는 show_comment() 에서 get 요청을 받고, 이에 대하여 response 하는 app.route 구문입니다.

minsoo 라는 몽고DB 컬렉션에 접근하여 레코드를 모두 가져오고, 이를 list 자료형으로 파싱하여 'result' 에 담아

return 을 해주고 있습니다.

 

 

 

6) post API - insert

function save_comment(){
    let name = $('#name').val();
    let comment = $('#comment').val();
    if (name === '' || comment === '') {
        alert('이름과 방명록의 내용을 작성해 주세요');
        return
    }
    $.ajax({
        type: 'POST',
        url: '/minsoo/insert',
        data: {name_give: name, comment_give: comment},
        success: function (response) {
            window.location.reload()
        }
    })
}

해당 JS 코드는 방명록을 입력받아 입력 버튼을 누를 경우, 값을 담고 /minsoo/insert 로 data 를 담아 POST 합니다.

 

flask 환경이 구현 되어 있는 app.py 에서는 inser post 요청을 다음과 같이 받고 있습니다.

@app.route("/minsoo/insert", methods=["POST"])
def intro_post():
    name_receive = request.form['name_give']
    comment_receive = request.form['comment_give']
    count = random.uniform(1, 1000)
    doc = {
        'num': count,
        'name': name_receive,
        'comment': comment_receive
    }
    db.minsoo.insert_one(doc);
    return jsonify({'msg': 'POST 연결 완료!'})

request 로 값을 받아서 변수에 담고, 넘버링으로서는 랜덤한 실수를 발생시킵니다.

이는 각 방명록 당 고유한 id 값을 주기 위한 요령 중 하나입니다. 가장 이상적인 것은 결코 같은 값이 나올 수 없는 함수는 구현하거나,

아니면 mongoDB에서 값이 저장될 경우 자동으로 할당해주는 ObjectId 를 이용하는 것이 최선입니다.

하지만 이 코드를 구현하던 당시의 저는 ObjectId 를 꺼내서 활용하는 방법을 몰랐었고, 단지 기능을 '되게끔' 만드는 데에만 급급하여 결코 좋지는 못한 코드를 짰었습니다. 

 

 

7) POST API - delete

function delete_msg (count) {
    const check = confirm('정말로 삭제하시겠습니까?');
    if (check) {
        $.ajax({
            type: "POST",
            url: "/minsoo/delete",
            data: {num_give: count},
            success: function (response) {
                alert(response["msg"]);
                window.location.reload();
            }
        });
    }
}
# 개인 소개 페이지에서 방명록 삭제 버튼 클릭 시 삭제 요청을 처리하는 구문.
@app.route("/minsoo/delete", methods=["POST"])
def intro_delete():
    count_receive = request.form['num_give']
    db.minsoo.delete_one({'num': float(count_receive)})

    return jsonify({'msg': '방명록 삭제 완료'})

위 코드는 페이지에서 '삭제' 버튼을 누를 경우, 해당 방명록을 DB에서 삭제시키고 클라이언트를 리로드 하는 기능을 구현하고 있습니다.

몽고DB에 어떠한 명령을 내리는 지만 다를 뿐, 기본적으로는 insert 와 크게 다를 바 없는 POST 통신입니다.

 

 

 

8) main 페이지 방명록

해당 방명록에서는 이름과 내용, 그리고 비밀번호를 입력합니다.

그렇게 해서 추가 된 방명록은 아래 목록의 가장 위로 올라오게 되고, 목록을 클릭할 시 UIr가 등장하며 수정할 지, 혹은 삭제할 지를 정할 수 있습니다. 이때 반드시 비밀번호를 매치시켜야 합니다.

 

 

 

9) main 페이지 게시판

해당 게시판의 게시글을 클릭할 경우, 상세 내용과 여러 옵션이 담긴 모달창이 display:none 상태에서 show() 합니다.

 

 

 

 

10) main 페이지의 방명록과 게시판에서 구현된 update.

function update_contents(num, name, time) {
    let content = $('#content' + num).children('textarea').val();
    let password = $('#password' + num).children('input').val();
    let timem = time;
    $.ajax({
        type: "POST",
        url: "/members/update",
        data: {
            name: name,
            password: password,
            content: content,
            timem: timem
        },
        success: function (response) {
            let s = response["msg"]
            if (s == '수정 완료') {
                alert(s);
                window.location.reload();
            } else {
                alert(s);
            }
        }
    });

위 코드는 main 페이지에서 방명록을 수정하고자 할 경우에 구동하는 함수입니다.

해당 함수에서 지역변수로 수정된 내용과 함께 비밀번호, 그리고 time 을 가져옵니다.

이 경우에서 개발자는 DB 레코드의 id 값으로서 그 레코드가 정확히 언제 insert 되었는지에 대한 시간 정보를 사용하고 있습니다.

즉, 고유값인 time 과 입력자가 보안을 유지하기 위해 함께 정한 비밀번호를 동시에 일치시켜야지만,

해당 DB 레코드를 update 시킬 수 있는 것입니다.

그렇다면, time 정보는 언제 가져와서, 언제 반영시킨 것일까요?

 

$(document).ready(function () {
    show_members();
     
});

.

.

.

function show_members() {
    //화면에 리스트 보여주기
    $.ajax({
        type: "GET",
        url: "/members/get",
        data: {},
        success: function (response) {
            let members = response['members'];
            let k = members.length - 1;
            let count = 0;
            let name = "";
            for (let i = k; i >= 0; --i) {
                name = members[i]['name'];
                let content = members[i]['content'];
                let time = members[i]['time'];
                let aa = "flush-collapse" + count;
                t = `<div class="accordion-item">
                <h2 class="accordion-header" id="flush-heading${count}">
                    <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
                            data-bs-target="#${aa}" aria-expanded="false"
                            aria-controls="flush-collapseThree"
                            onclick="closed_roll('flush-heading${count}',${count})"
                            >
                        ${name}
                    </button>
                </h2>
                <div id="${aa}" " class="accordion-collapse collapse" aria-labelledby="flush-heading'${count}'"
                     data-bs-parent="#accordionFlushExample">
                    <div class="accordion-body">
                        <span id="con${count}">${content}</span>&nbsp&nbsp&nbsp<i class="fa-solid fa-pen pen${count}" onclick="show_comment(${count})"></i>
                         <button type="button" style="float: right; right: 0px" onclick="delete_content(${count},${time})" class="btn btn-danger btn${count}">삭제</button>
                        <div class="form-floating" id="content${count}"style="display: none">
                        .
                        .
                        .

이렇게 html 로드 직후 get으로 DB에서 방명록 list 를 가져올 때, 고유값인 time 도 함께 가져와서 미리 delete 와 update 기능을 하는 버튼에 onclick 메소드의 매개변수로서 담아두었습니다.

for 문을 돌리면서 매번 생성되는 html 태그에 해당 방명록마다 그에 맞는 time 값을 넣었기에, 어느 방명록을 수종, 혹은 삭제 하더라도

그에 매칭하는 time 값을 찾아서 활용할 수 있습니다.

 

 

 

 

 

3. 종합

당초에 이 '팀 소개 미니 프로젝트' 에서 저희 조가 추구하고자 했던 방향성과, 프로젝트가 완성되었을 때 어떠한 기능과 서비스를 구현할 수 있는지를 비교해 본다면, 우선 컨셉적인 부분에서는 일련의 성과를 거둘 수 있었다, 라고 말씀 드릴 수 있을 것 같습니다.

저희 조가 미니 프로젝트를 준비하며 목표로서 잡은 바는 다음과 같았습니다.

   1. 웹개발 종합반에서 배운 기능의 응용 및 활용

   2. 미니 프로젝트를 통한 협업을 체험

   3. 우리가 전달하고자 하는 메세지와 컨셉을 감각적으로 페이지에 잘 담을 수 있었는가.

 

우선 저희는 불완전하고 불정비된 상태이긴 하지만 API 를 구현화 할 수 있었고(restfull 하다고는 확언하기 어렵습니다만...),

협업을 체험함으로서 업무를 체계화 시키고 모듈화 시키는 것이 얼마나 중요한 것인지 실감할 수 있었습니다.

    * 왜냐하면, 협업을 한 다는 것은 각자 역할을 나눈 다는 것인데, 그것이 저 개인적으로는 정말 어려웠던 것 같습니다.

그리고 node.js 트랙을 밟아 가는데에 있어서 우리가 당장의 실적은 좋지 못할 지언정 결국에는 노력하여 끝끝내 완수해 내겠다는, 적어도 포기하지는 않겠다는 끈기를 스스로 확인할 수 있는 계기가 되기도 하였습니다.

 

무엇보다 큰 수확은, 팀원들이 서로 서로 정보와 지식을 공유하여 서로의 부족한 점을 매꿀 수 있음을 확인한 바에 있습니다.

또한 평소에 '나는 이러한 기능에 대해 잘 알고 있다' 라고 막연히 생각만 하던 부분도, 막상 팀원들에게 설명하려 하니 

'구체적으로 어떤 단어를 사용해야 이 지식이 보다 더 다른 사람에게 잘 전달될 수 있을까' 라고 고민하게 되었습니다.

 

 

 

3-1) 어려웠던 점, 해결하지 못했던 부분들.

API 를 구현화 하면서 가장 크게 어렵다고 느꼈던 부분은 

            " 나는 이 방명록을 삭제하고 싶은데, 몽고DB 는 내가 삭제하고 싶은 방명록을 어떻게 알 수 있을까? "

라는 부분이었습니다.

네, 바로 데이터 레코드의 고유값인 ID 필드입니다.

 

count = list(db.teams.find({}, {'_id': False})) #db 총 열의 개수 카운트
num = len(count) + 1
count = random.uniform(1, 1000)
notice_id = 0

print(date)

notices_list = list(db.notice_board.find({}, {'_id': False}).sort('notice_id', -1))

if notices_list == []:
    notice_id = 1
else:
    notice_id = notices_list[0]["notice_id"] + 1

저희는 몽고DB의 ObjectId 값을 가져오는 방법을 알지 못했기에, 그리고 무엇보다 id 값으로 특정 데이터 레코드를 지정할 수 있는 것이 서버 - 클라이언트 간의 통신에 있어서 얼마나 기초적이며 중요한 것인지에 대한 개념이 잡혀 있지 않은 상태였습니다.

따라서 어떻게 id 값을 설정할 지, html 로 id 값을 가져왔을 때는 어떠한 방식으로 보관해야 할지 등등을 많이 생각하였습니다.

 

 

 

let temp = `
            <div class="card" style="margin-bottom: 20px;">
                <div class="card-body" id="${count}">
                    <blockquote class="blockquote mb-0" style="float: left;">
                        <p>${comment}</p>
                        <footer class="blockquote-footer">${name}</footer>
                    </blockquote>
                    <div style="float:right; margin-top:10px;">
                        <button type="button" class="btn btn-danger" onclick="delete_msg(${count})">삭제</button>
                    </div>
                </div>
            </div>`
$('#comment-list').append(temp);
let temp_html = `<div class="card">
                    <div class="card-body">
                        <blockquote class="blockquote mb-0">
                            <p>${comment}</p>
                            <footer class="blockquote-footer">${name}</footer>
                        </blockquote>
                    </div>
                    <div class="delbtn">
                    <button onclick="tkdel(${num})"
                            type="button"
                            class="btn btn-dark">
                            삭제</button>
                    </div>
                </div>`
$('#comment-list').append(temp_html)

 

누군가는 id 값을 가져와서는 html 의 id 속성에 넣기도 하였고, 누군가는 아예 그냥 id 값이 필요한 함수에 바로 매개변수로 삽입하기도 하였습니다. 만약 저희가 vue 나 react 를 사용했다면, 컴포넌트에 data 를 저장하여 라이프 사이클을 조절하며 원하는 타이밍에 원하는 곳으로 데이터를 쉽게 보낼 수 있었을 겁니다.

하지만, 몇 년 전까지만 해도 html6 를 배워보기 전에 html5 를 먼저 배우고, 기존에는 어렵고 까다로운 기능들이 기술의 발전에 따라 얼마나 구현하기 쉽게 변화되었는지를 체감할 수 있는 순간이었습니다.

만약 저희가 나중에 node.js 공부를 하며 기회가 생겨 vue 나 react 를 사용하게 된다면, 

        " 와, 그때는 어려웠던 기능들이 지금은 정말 체계적이고 쉽게 다룰 수 있게 되었구나 "

라고 느끼게 될 지도 모릅니다. 그리 된다면 저희는 편리하고 체계적인 기능들이 사실은 어떠한 로직으로 형성되어 있는 것들인지를 알 수 있게 될 겁니다. 또, 그런 기술의 진보 덕에 개개인이 얼마나 큰 편리함을 얻게 되는지도 체감하게 될 것 입니다.