강의 예제로 할일 앱 프론트엔드 부분 작업을 진행했다.
강의의 경우 리액트 기반으로 프론트엔드를 제공하는데 나는 리액트로 프론트엔드를 진행하지 않고 생 자바스크립트로 진행하기 때문에 처음부터 끝까지 직접 구현해보았다. (물론 gpt에게 물어가면서)
서버 데이터를 가져오는 방식은 fetch api 방식을 썼다.
처음에는 읽기, 생성, 수정, 삭제 네가지 fetch를 만들어서 진행했었는데 진행하다보니 생성을 제외한 케이스들은 처음 fetch를 실행하는 방식이 일치해서 getTasks라는 하나의 함수로 묶이게되었다.
'할 일'이라는 리소스를 tasks 라는 url 로부터 가져와서 할일 목록을 만들어냈다. (읽기)
function getTasks () {
fetch ( "http://localhost:5001/tasks" )
. then (( response ) => response . json ())
. then (( data ) => {
console . log ( data );
// 기존 목록 초기화
const todoList = document . getElementById ( "todo-list" );
todoList . innerHTML = "" ;
// 할 일 목록이 비었는지 확인
if ( data . data . length === 0 ) {
todoList . innerHTML = `
<p>할 일 목록이 없습니다.</p>
` ;
return ; // 더 이상 작업하지 않고 함수 종료
}
// 목록이 있을 때 할 일 카드 생성
data . data . forEach (( item , i ) => {
const todoCard = `
<div class="todo-card" id="todo-task ${ i } ">
<p> ${ item . task } </p>
<span>
<input type="button" id="is-complete ${ i } " value="">
<input type="button" id="delete-btn ${ i } " value="삭제">
</span>
</div>
` ;
todoList . insertAdjacentHTML ( "beforeend" , todoCard );
다만 수정과 삭제는 tasks의 정보로 생성된 버튼들에 이벤트를 발생시켜서 한번 더 fetch api를 진행하는데 이 때 수정 및 삭제가 진행된다. 수정이나 삭제는 tasks라는 컬렉션 안에 있는 개별적인 아이템을 잡아서 수정이나 삭제를 해야하기 때문에 url을 tasks/고유아이디 주소로 api를 진행한다.
const completeBTN = document . getElementById ( `is-complete ${ i } ` );
const deleteBTN = document . getElementById ( `delete-btn ${ i } ` );
// 완료/미완료 버튼 상태 설정
completeBTN . value = item . isComplete ? "다시하기" : "완료하기" ;
// 완료/미완료 토글 이벤트 리스너 추가
completeBTN . addEventListener ( "click" , () => {
let changeComplete = completeBTN . value === "완료하기" ? true : false ;
fetch ( `http://localhost:5001/tasks/ ${ item . _id } ` , {
method : "PUT" ,
headers : {
"Content-Type" : "application/json" ,
},
body : JSON . stringify ({
isComplete : changeComplete ,
}),
})
. then (( response ) => response . json ())
. then (( data ) => {
console . log ( "PUT response:" , data );
// 버튼 상태를 즉시 업데이트
completeBTN . value = changeComplete ? "다시하기" : "완료하기" ;
})
. catch (( error ) => console . error ( "Error:" , error ));
});
할 일 완료 버튼을 boolean 방식으로 업데이트를 진행했는데 이부분이 은근히 어려웠다.
삭제의 경우 오히려 방식이 간단해서 쉽게 처리했다.
// 삭제 버튼 이벤트 리스너 추가
deleteBTN . addEventListener ( "click" , () => {
fetch ( `http://localhost:5001/tasks/ ${ item . _id } ` , {
method : "DELETE" ,
})
. then (( response ) => response . json ())
. then (( data ) => {
console . log ( "DELETE response:" , data );
// 삭제 후 목록을 다시 불러와 갱신
getTasks ();
})
. catch (( error ) => console . error ( "Error:" , error ));
});
생성의 경우 getTasks() 함수와 별개로 인풋 옆 할 일 추가 버튼을 눌렀을 때 'POST'가 진행될 수 있게 세팅되었다.
body 안쪽 task 값은 인풋에 입력한 정보가 들어갈 수 있게 설정해주었고 버튼 실행 시 인풋 창 안에 입력했던 정보는 다시 비워지게 만들어줬다.
// (생성)
document . getElementById ( "post-btn" ). addEventListener ( "click" , () => {
let inputElement = document . getElementById ( "input-task" );
let inputValue = inputElement . value ;
// 값이 없으면 에러 메시지 출력 (간단한 밸리데이션)
if ( ! inputValue . trim ()) {
console . error ( "Task cannot be empty!" );
return ;
}
// POST 요청
fetch ( "http://localhost:5001/tasks" , {
method : "POST" ,
headers : {
"Content-Type" : "application/json" ,
},
body : JSON . stringify ({
task : inputValue ,
isComplete : false ,
}),
})
. then (( response ) => response . json ())
. then (( data ) => {
console . log ( "POST response:" , data );
// POST 요청 후 목록 갱신
getTasks ();
// 입력 필드 비우기
inputElement . value = "" ; // 여기가 입력 필드를 비우는 부분입니다
})
. catch (( error ) => console . error ( "Error:" , error ));
});
이렇게 심플하게 완성 해 본 할일 앱! (디자인은 최대한 신경쓰지 않았다. 오직 개발 공부 용)
전반적으로 잘 작동했다.
프론트-서버-DB까지 한번에 다뤄보니 웹 개발의 전체적인 개념이 좀 더 명확해지는 것 같다.