작업기간: 2023.09.26 ~ 2023.10.06 (10일)
📑Backend Repository: https://github.com/jhcode33/react-spring-blog-backend
📑Frontend Repository: https://github.com/jhcode33/react-spring-blog-frontend
React와 Spring boot를 활용한 SPA 게시판 프로젝트입니다. 아래와 같은 문제에 대해 고민하며 프로젝트를 진행했고, 최선의 해결책을 찾아서 코드를 작성했습니다.
- 회원 인증/인가(JWT)
- N + 1 문제
- Entity 양방향 관계, JSON 직렬화 문제
지금까지 배운 것을 기반으로 SPA 방식의 프로젝트를 처음으로 구현해보았습니다.
웹 프로그래밍의 기본 소양으로 여겨지는 게시판을 직접 만들어 보면서, 효율적인 데이터 처리와 데이터 변환 등 다양한 개발 문제에 직면하고 해결책을 찾아갔습니다.
- CRUD + Rest API + SPA
- 개발 기간 : 2023.09.26 ~ 2023.10.06
- 참여 인원 : 1명
-
게시판 기능
- 모든 게시글 및 특정 게시글 조회
- 게시글 검색 (제목, 내용, 작성자)
- 게시글 작성 [회원]
- 게시글 수정 [회원, 게시글 작성자]
- 게시글 삭제 [회원, 게시글 작성자]
- 게시글 답글 작성 [회원]
-
댓글 기능
- 댓글 조회
- 댓글 작성 [회원]
- 댓글 수정 [회원, 댓글 작성자]
- 댓글 삭제 [회원, 댓글 작성자]
-
회원 기능 + JWT
- 회원가입
- 로그인/로그아웃
![DB-ERD](https://private-user-images.githubusercontent.com/125725072/272663501-0a8b5201-7e37-4185-949b-284120ae8aa5.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxMTI2MjQsIm5iZiI6MTcyNDExMjMyNCwicGF0aCI6Ii8xMjU3MjUwNzIvMjcyNjYzNTAxLTBhOGI1MjAxLTdlMzctNDE4NS05NDliLTI4NDEyMGFlOGFhNS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQwMDA1MjRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT05YzYyMTllNmQ0MjUwYzYzODM5MjBhNDEzYjI4NzA4NGUwYzhmYTRhNTJiODE4ODI2YmFhZWFlOTNhN2YwMDdhJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.1hx-DKk18FDnLFIDbvvIEQN6nHLNIKiaeFA2mO6Jkio)
![BoardTable](https://private-user-images.githubusercontent.com/125725072/272663576-4d1b77de-99dc-4d77-8cd2-7724e14d6c08.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxMTI2MjQsIm5iZiI6MTcyNDExMjMyNCwicGF0aCI6Ii8xMjU3MjUwNzIvMjcyNjYzNTc2LTRkMWI3N2RlLTk5ZGMtNGQ3Ny04Y2QyLTc3MjRlMTRkNmMwOC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQwMDA1MjRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT00NmU0OWMyNmY0OTYzNzQyMDllNTA4ZDRhYmJhMWUxYmFkNmExYjhjYWUyYmE2MjVlNWQ2YWI3ODZhNGQ3YmM2JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.NWdzStEzacVGtn-XoJR7ewqPN9ZkPe6iBmvlUg19pts)
![CommentTable](https://private-user-images.githubusercontent.com/125725072/272663581-7c46572e-0c52-4347-8388-40bdd8356db5.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxMTI2MjQsIm5iZiI6MTcyNDExMjMyNCwicGF0aCI6Ii8xMjU3MjUwNzIvMjcyNjYzNTgxLTdjNDY1NzJlLTBjNTItNDM0Ny04Mzg4LTQwYmRkODM1NmRiNS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQwMDA1MjRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1kZGUyMTg3ZTNlYzE1ZDQ0NDNlNGNkMjg2YTNlMzAxMWMxNWQwNjkyZGY0ZjlmZGRhYjNiYTk4NmM5YTcyNTI3JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.xIb-eCWpxi34IoqwHp0a2xpHOzxIc6ZgmCx9pZRi_Ig)
![FileTable](https://private-user-images.githubusercontent.com/125725072/272667907-c50f8267-01d8-4c54-a066-7abf1cf6684a.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxMTI2MjQsIm5iZiI6MTcyNDExMjMyNCwicGF0aCI6Ii8xMjU3MjUwNzIvMjcyNjY3OTA3LWM1MGY4MjY3LTAxZDgtNGM1NC1hMDY2LTdhYmYxY2Y2Njg0YS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQwMDA1MjRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1lNzRhZDk4NjU0ZjRiYzc5ZmNlNTJjOTE4MmNhMDEwNWNiYWI3NWE0NmM5NzZmMzA1OWU3YTRmYjA2NzllNzhmJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.E8g4pV954VJ44RsUncuO2YPhusvaG7TN1Az8Z_mGfRE)
![MemberTable](https://private-user-images.githubusercontent.com/125725072/272668168-e33d8344-7988-4b72-bd82-583a4fee7218.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxMTI2MjQsIm5iZiI6MTcyNDExMjMyNCwicGF0aCI6Ii8xMjU3MjUwNzIvMjcyNjY4MTY4LWUzM2Q4MzQ0LTc5ODgtNGI3Mi1iZDgyLTU4M2E0ZmVlNzIxOC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQwMDA1MjRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0zM2UxZTgyY2JiNDc0ODFlYzlhODg1ZDE1NDdlYTI4YTFkN2Q1MWEzNGUwY2ZkMDIzNmNiZTI2YzFkYjAwOTE0JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.vPNqdYCR1qwEJ6zz3UdquEvl9PHRK5qtvHFNv7278RQ)
![MemberAPI](https://private-user-images.githubusercontent.com/125725072/272663969-a16121d2-fc55-4761-9fda-d1af53b60eb1.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxMTI2MjQsIm5iZiI6MTcyNDExMjMyNCwicGF0aCI6Ii8xMjU3MjUwNzIvMjcyNjYzOTY5LWExNjEyMWQyLWZjNTUtNDc2MS05ZmRhLWQxYWY1M2I2MGViMS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQwMDA1MjRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT03MmM5ZWVjOWZmMThhMjQzZjVjZjk3NTNhYjJkZDdjMjlhNDY5MmVjOGMxNjBlYzI1YWEyODZhYTU5NWExNGQzJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.g5NRGj6elAsNkDU-tRNnuhkB8rI1mjFL5TE-bntKd2s)
![BoardAPI](https://private-user-images.githubusercontent.com/125725072/272667440-5467f0b6-e585-4881-8923-07087080acc6.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxMTI2MjQsIm5iZiI6MTcyNDExMjMyNCwicGF0aCI6Ii8xMjU3MjUwNzIvMjcyNjY3NDQwLTU0NjdmMGI2LWU1ODUtNDg4MS04OTIzLTA3MDg3MDgwYWNjNi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQwMDA1MjRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1lM2MwMjQ1YjdmZDJiNmUyYjVlNmI5NTA5YjNiOGFlOGVmZTU0YjBhM2RiMzYzZjUzNzA5NjE1ZmVhYzRmOTU4JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.ADpFQeDeGfjJMc5JGj0ee5wiGaKIODVNNORFmqVDzOE)
![CommentAPI](https://private-user-images.githubusercontent.com/125725072/272663958-b6b80f61-1c1f-44df-bcd5-e3ba2e678a0f.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxMTI2MjQsIm5iZiI6MTcyNDExMjMyNCwicGF0aCI6Ii8xMjU3MjUwNzIvMjcyNjYzOTU4LWI2YjgwZjYxLTFjMWYtNDRkZi1iY2Q1LWUzYmEyZTY3OGEwZi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQwMDA1MjRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1hZmM2NTViMGIzYmJhYmM5OTE0MjE4NmJmNDc2YzVmZTY5M2YzMGY0YzkxNTJiYTA1YzgyOWUyZGVlOTBjNTVlJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.KMFxzysd3r3CxsSCKkX5ocV0l25thPsPG_bxoVtTJik)
![MemberTable](https://private-user-images.githubusercontent.com/125725072/272663965-c6ee6ea3-6bfd-45af-bccf-eb9e41053089.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxMTI2MjQsIm5iZiI6MTcyNDExMjMyNCwicGF0aCI6Ii8xMjU3MjUwNzIvMjcyNjYzOTY1LWM2ZWU2ZWEzLTZiZmQtNDVhZi1iY2NmLWViOWU0MTA1MzA4OS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQwMDA1MjRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0yYjQ3NGU5NTEwMWJlYjVlYzM2NDI4NTM4YTVjMTgzN2QxMDlhNmM5OWY2MDU4ZTg2YjJhY2QyODk2M2ZlN2UyJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.w3Na8DKrG9qFgFG0bGnq1RviqjxUa-dVryYSqM-JJ-o)
기술 | 버전 |
---|---|
Spring Boot | 3.1.4 |
Spring Web | 3.1.0 |
Spring Security | 3.0.4 |
Spring Data Jpa | 3.0.4 |
Bean Validation | 3.1.0 |
JSON Web Token | 0.11.5 |
MySQL Connector J | 8.0.32 |
기술 | 버전 |
---|---|
NodeJS | 16.16.0 |
React | 18.2.0 |
axios | 0.27.2 |
react-axios | 2.0.6 |
react-dom | 18.2.0 |
react-js-pagination | 3.0.3 |
react-router | 6.3.0 |
react-router-dom | 6.3.0 |
react-scripts | 5.0.1 |
-
Jh's Notion: @OneToMany Paging, N + 1 해결
연관된 Comment Entity를 in 쿼리로 한 번에 가져옵니다. N + 1의 문제를 1 + 1로 해결합니다. 자세한 것을 링크에 기록해두었습니다.
-
Jh's Notion: DTO에 관한 생각
Request와 Response로 정확한 데이터를 주고 받고, JSON 직렬화 문제를 해결하기 위해 DTO 클래스를 사용했습니다. Entity를 응답으로 반환하는 것이 아닌 DTO로 매핑하여 반환할 수 있도록 static 메소드를 통해서 DTO <-> 엔티티 간의 변환을 했습니다.
-
React는 처음이라 프론트 작업을 할 때 시간이 많이 소요되었습니다. 백엔드 API에 집중하던 시간보다 프론트엔드 작업에 소모했던 시간이 더 많아서 아쉽습니다. 그로 인해 JWT, Security 등 관련 코드를 깊게 볼 시간이 부족했던 점이 많이 아쉬웠습니다.
-
JWT 관련 로직을 조금 더 자세하게 들여다보고 싶었지만 간단하게 구성만 한 부분이 아쉽습니다. 작업 기간인 10일 이후에도 꾸준히 보강해서 업그레이드 해보겠습니다.
-
테스트 코드를 작성하지 못한게 아쉽습니다. PostMan을 통해서 모든 로직을 검증했지만, 테스트 코드로 검증했다면 조금 더 이슈나 에러 관리에 쉽지 않았을까라는 생각을 합니다.
지금까지 배워왔던 것을 토대로 SPA 블로그를 처음 구현했습니다. React가 처음이지만, 처음이라도 관련 자료를 찾아보면서 ‘구현’할 수 있도록 노력했습니다. 배웠던 것만 사용하는 것이 아니라, 배우지 않은 부분도 부딪히면서 구현하고 완성시키려고 했던 시간들이 가장 기억에 남습니다.
강의와, 책에서 공부한 예제들과 블로그 및 여러 커뮤니티에서 문제와 에러에 대한 해결책을 찾을 수 있었습니다. “DTO ↔ Entity 간에 변환 로직을 어떻게, 어디에 작성해야 하는지”, “N + 1 문제를 어떻게 해결하는지” 등 문제에 대해 고민하고, 올바르게 검색하는 방법을 통해서 해결책을 찾아 갔습니다.
프로젝트를 통해 배웠던 기술들을 사용해 보면서 앞으로 내가 필요한 기술과 더욱 채워야할 지식 등을 더 잘 알 수 있게 되었습니다. 탄탄하고 체계적인 설계와 테스트 코드가 얼마나 중요한지 깨닫는 시간이었습니다. 새로운 기술과 코드에 두려워하지 않고, 체계적이고 튼튼한 서비스를 구축하는 개발자가 되어야겠다고 다짐하게 되는 프로젝트였습니다.
긴 글 읽어주셔서 감사합니다! 🤗