Fullstack 93

heo4·2026년 8월 27일

Fullstack

목록 보기
48/70

풀스택

React + Spring Boot로 TodoList 만들기

프로젝트 개요

평가용 과제로 TodoList 서비스를 만들었다. 평가는 프론트엔드 위주로 진행되지만, 실제 동작하는 서비스를 보여주기 위해 백엔드까지 직접 구성했다.

  • 프론트엔드: React 19 + Vite + Tailwind CSS
  • 백엔드: Spring Boot 4.1.1 + Spring Data JPA
  • DB: H2 (인메모리)

요구사항

  • 할 일(Todo) CRUD — 각 Todo는 제목 / 내용 / 마감일 / 완료 여부 / 카테고리로 구성
  • 완료 처리, 마감일 임박(24시간 이내) 항목 구분 표시 등 상태 관리
  • 페이지네이션으로 목록 조회
  • Git으로 개발 이력 관리, README에 API 명세 및 ERD 정리

구현 과정

1. 프로젝트 초기 세팅

Spring Initializr로 백엔드(Gradle, Web/JPA/H2/Lombok 의존성)를 받고, npm create vite로 React 프론트를 세팅했다. Tailwind CSS는 @tailwindcss/vite 플러그인으로 붙였다.

2. 기본 CRUD 구현

처음엔 단순히 title 하나만 가진 Todo로 시작했다. 등록 / 조회 / 완료 토글 / 삭제 API를 만들고, 프론트에서 입력폼 → 목록 → 완료 체크 → 삭제까지 한 사이클을 먼저 완성했다.

3. 필드 확장

과제 요구사항에 맞춰 Todo를 제목 / 내용 / 마감일 / 완료여부 / 카테고리 구조로 확장했다.

  • 백엔드: Todo 엔티티에 content(내용), dueDate(LocalDate), category 필드 추가
  • 프론트: TodoForm에 내용 textarea, 마감일 date picker, 카테고리 select 추가
  • TodoItem에 카테고리 배지, 내용 미리보기, 마감일 표시 추가

4. 필터 & 일괄 삭제

전체 / 미완료 / 완료 상태별 필터 버튼을 추가하고, "완료 항목 삭제" / "전체 삭제" 버튼도 만들었다. 대상이 없을 땐 버튼이 자동으로 비활성화되도록 처리했다.

5. 마감일 임박 표시

요구사항에 있는 "마감 임박(24시간 이내) 항목 구분 표시"를 위해, 마감일 기준으로 상태를 계산하는 함수를 만들었다.

function getDueStatus(dueDate, completed) {
  if (!dueDate || completed) return null;
  const due = new Date(`${dueDate}T23:59:59`);
  const diffHours = (due.getTime() - Date.now()) / (1000 * 60 * 60);
  if (diffHours < 0) return "overdue";   // 기한 초과
  if (diffHours <= 24) return "imminent"; // 마감 임박
  return null;
}

기한이 지난 항목은 빨간 배지("기한 초과"), 24시간 이내로 남은 항목은 주황 배지("마감 임박")로 표시했다.

6. 페이지네이션

목록이 길어질 걸 대비해 백엔드에 페이지네이션을 붙였다. Spring Data의 Page<Todo>를 활용해서 filter, page, size 쿼리 파라미터를 받도록 구성하고, 프론트에는 이전/다음 버튼을 달았다. 항목을 삭제해서 현재 페이지가 비어버리면 자동으로 이전 페이지로 돌아가도록 보정 로직도 넣었다.

7. Git / README 정리

git init 후 커밋 이력을 쌓고 GitHub에 push했다. README에는 기술 스택, 실행 방법, ERD, API 명세를 정리해서 누구든 클론받아 바로 실행해볼 수 있도록 했다.

컴포넌트 구조

상태 관리와 API 호출은 App.jsx에 몰아넣고, 나머지는 props로 데이터/콜백만 받는 표현 컴포넌트로 나눴다.

App.jsx (컨테이너 — 상태 + API 호출)
 ├─ TodoForm.jsx   (입력 폼)
 ├─ TodoList.jsx   (목록 래퍼)
 │    └─ TodoItem.jsx  (개별 항목 렌더링 + 마감상태 계산)
 └─ 페이지네이션 버튼

API 명세

Base URL: /api/todos

MethodEndpoint설명
GET/api/todos?filter=&page=&size=목록 조회 (필터 + 페이지네이션)
GET/api/todos/active-count미완료 개수
POST/api/todos할 일 등록
PATCH/api/todos/{id}/toggle완료 여부 토글
DELETE/api/todos/{id}단건 삭제
DELETE/api/todos/completed완료 항목 일괄 삭제
DELETE/api/todos전체 삭제

배운 점

  • 프론트-백엔드 필드 동기화: 백엔드 필드명(title ↔ text)이 바뀔 때마다 프론트의 API 함수, 컴포넌트 props까지 같이 맞춰줘야 해서, 처음부터 필드 이름을 확정하고 가는 게 훨씬 편하다는 걸 체감했다.
  • 페이지네이션 + 필터 조합: 백엔드에서 필터링과 페이지네이션을 동시에 처리하니, 프론트 상태(현재 페이지, 필터)가 바뀔 때마다 다시 요청하는 흐름을 잘 설계해야 했다. 특히 "삭제 후 현재 페이지가 비는 경우"를 자동으로 이전 페이지로 보정하는 부분이 은근히 신경 쓸 게 많았다.
  • 작은 기능부터 검증하며 쌓기: 처음부터 모든 필드/기능을 다 넣기보다, 기본 CRUD → 필드 확장 → 필터 → 상태표시 → 페이지네이션 순으로 하나씩 완성하고 넘어가니 디버깅 범위가 항상 작았다.

0개의 댓글