기간: (2024.06 ~ .)
개인 블로그 서비스입니다.
학부에서 여러 주제로 팀 프로젝트를 진행해봤지만 실질적으로 유지 보수 및 운영하고 있는 프로젝트가 없었습니다. 단순히 설계와 구현에만 집중하고 있었다는 사실을 깨닫고, 지속적으로 운영 및 유지 보수할 수 있는 프로젝트의 필요성을 느꼈습니다.
- 실제 서비스 운영에서 겪을 수 있는 문제들을 직접 경험하고 개선할 수 있도록 합니다.
- 지속적으로 코드 품질 개선합니다.
- 위와 같은 활동들을 통해 자연스럽게 포트폴리오를 강화합니다.
- 개인 브랜딩을 통해 가치를 높일 수 있도록 합니다.
https://github.com/users/JongDeug/projects/9/views/3
https://github.com/users/JongDeug/projects/9/views/1
https://jongdeug.port0.org/api/docs
https://github.com/JongDeug/blog-frontend
기술 | 설명 |
---|---|
Express | 학부 때 사용 경험 있음. 빠른 API 개발 가능 |
NestJS | 자유도가 높은 Express 대신 계층화 및 모듈화 방식을 사용하는 NestJS로 마이그레이션. 코드 가독성과 유지 보수성 향상 기대 |
JavaScript | 프론트엔드와 백엔드 모두 하나의 언어로 개발 가능 |
TypeScript | 코드의 안전성과 유지 보수 향상 |
Prisma | TypeORM보다 직관적인 스키마 작성 가능. 좀 더 편리하게 데이터베이스 관리 및 쿼리 수행 |
MySQL | 관계형 데이터베이스 설계 경험 목적 |
Swagger | 학부 팀 프로젝트에서 경험한 명확한 소통의 필요성에 따라 API 문서화 도구 적용 |
JWT를 이용한 사용자 인증 시스템에서 여러 이슈 발생:
- Refresh Token을 서버 DB에 저장하는 이유에 대한 궁금증
- 클라이언트에게 JWT 토큰을 안전하게 전달하는 방법
1. 토큰 무효화
- 초기 구현: 서버 DB에 Refresh Token 저장 → 토큰 무효화 가능
- 개선: 캐시를 활용해 DB 접근 없이 토큰 무효화가 가능하도록 변경 → 성능 최적화
2. 토큰 캐싱
- 초기 구현: 요청마다 Access Token 토큰 검증
- 개선: Access Token을 캐싱해 검증된 유저 응답 시간 단축
3. 토큰 전송 방식
- 쿠키에 HttpOnly, SameSite, Secure 속성을 적용해 안전하게 전달
1. 토큰 무효화
- 탈취 위험에 대비 가능
2. 성능 개선
- Access Token을 사용하는 API 응답 시간: 37ms → 7ms (약 81% 단축)
- Refresh API 응답 시간: 95ms → 73ms (약 23% 단축)
- Logout API 응답 시간: 90ms → 57ms (약 36% 단축)
3. 보안 강화
- XSS, CSRF, Sniffing 공격 방어 가능
- 초기 구현: 게시글 Create API에 이미지 업로드 기능 포함
- 프론트엔드와 연결하는 과정에서 이미지 기능 개선 필요성을 느낌
1. 이미지 업로드 API 독립적 분리
2. Nginx 도입
- WAS(Web Application Server)에 도달하기 전에 Nginx에서 정적 이미지 전달
3. Task Scheduling 활용
- 하루마다 업로드된 지 24시간이 지난 이미지를
temp
폴더에서 삭제
- 게시글 작성 API 응답 시간 72.16ms → 22.96ms (약 68% 단축)
- 게시글 작성 취소 및 페이지 이탈 시 발생하는 불필요한 이미지 문제 해결
- 이미지 로딩 속도 향상
이전 버전에서 코드 분리가 미흡해 가독성과 유지 보수 어려움 발생
NestJS 도입
- 계층화 및 모듈화 구조인 NestJS를 사용해 유지 보수성 향상
- 프레임워크(Ioc Container)가 대신 의존성 문제를 관리해 주기 때문에 코드 구현에 집중 가능
코드 가독성 및 확장성 개선
🔥 테스트
- 유닛 테스트, 통합 테스트, E2E 테스트 진행
- 버그를 빠르게 발견할 수 있었고 코드의 신뢰성을 높임
🔥 Docker
- 라즈베리파이에서 따로 환경 세팅을 하지 않고도 서버 구동 가능
- Multi-state builds 빌드 적용 697MB → 474MB (약 31% 절감)
🔥 CI/CD
- Github Actions를 활용해 테스트, 빌드, 배포가 자동으로 이뤄지는 파이프라인 구축
- 코드 변경 사항을 자동으로 프로덕트 환경에 배포할 수 있어 편리함
구분 | 기능명 | HTTP Method | REST API | JWT | ROLE(하위 범주) |
---|---|---|---|---|---|
1. 회원 관리 | 1.1 이메일 가입 | POST | /auth/register | X | ALL |
1.2 로그인 | POST | /auth/login | X | ALL | |
1.3 로그아웃 | GET | /auth/logout | O | USER | |
1.4 로그인 갱신 | GET | /auth/token/refresh | O | USER | |
1.5 토큰 무효화 | GET | /auth/token/revoke/:id | O | ADMIN | |
1.6 유저 목록 조회 | GET | /user | O | ADMIN | |
1.7 유저 상세 조회 | GET | /user/:id | O | ADMIN | |
1.8 유저 삭제 | DELETE | /user/:id | O | ADMIN | |
2. 게시글 관리 | 2.1 게시글 목록 조회 | GET | /post?search=&take=&draft=&cursor=&order[] | X | ALL |
2.2 게시글 상세 조회 | GET | /post/:id | X | ALL | |
2.3 게시글 등록 | POST | /post | O | ADMIN | |
2.4 게시글 수정 | PATCH | /post/:id | O | ADMIN | |
2.5 게시글 삭제 | DELETE | /post/:id | O | ADMIN | |
2.6 게시글 좋아요 | POST | /post/like/:id | X | ALL | |
2.7 이미지 업로드 | POST | /common/image | O | ADMIN | |
3. 댓글 관리 | 3.1 댓글 작성(회원) | POST | /post/comment/user | O | USER |
3.2 댓글 수정(회원) | PATCH | /post/comment/user/:id | O | USER | |
3.3 댓글 삭제(회원) | DELETE | /post/comment/user/:id | O | USER | |
3.4 댓글 작성(비회원) | POST | /post/comment/guest | X | ALL | |
3.5 댓글 수정(비회원) | PATCH | /post/comment/guest/:id | X | ALL | |
3.6 댓글 삭제(비회원) | DELETE | /post/comment/guest/:id | X | ALL | |
4. 태그 관리 | 4.1 태그 목록 조회 | GET | /tag | X | ALL |
4.2 태그 상세 조회 | GET | /tag/:id | X | ALL | |
4.3 태그 생성 | POST | /tag | O | ADMIN | |
4.4 태그 수정 | PATCH | /tag/:id | O | ADMIN | |
4.5 태그 삭제 | DELETE | /tag/:id | O | ADMIN | |
5. 카테고리 관리 | 5.1 카테고리 목록 조회 | GET | /category | X | ALL |
5.2 카테고리 상세 조회 | GET | /category/:id | X | ALL | |
5.3 카테고리 생성 | POST | /category | O | ADMIN | |
5.4 카테고리 수정 | PATCH | /category/:id | O | ADMIN | |
5.5 카테고리 삭제 | DELETE | /category/:id | O | ADMIN |
erDiagram
"GuestComment" {
Int id PK
String nickName
String email
String password
String guestId FK
}
"Guest" {
Int id PK
String guestId UK
}
"Category" {
Int id PK
String name UK
DateTime createdAt
DateTime updatedAt
}
"Comment" {
Int id PK
String content
DateTime createdAt
DateTime updatedAt
Int parentCommentId FK "nullable"
Int postId FK
Int authorId FK "nullable"
Int guestId FK "nullable"
}
"Image" {
Int id PK
String url
Int postId FK
}
"PostLike" {
Int postId FK
String guestId FK
DateTime createdAt
}
"Post" {
Int id PK
String title UK
String content
DateTime createdAt
DateTime updatedAt
Int prevId "nullable"
Int nextId "nullable"
Boolean draft
String summary
Int authorId FK
Int categoryId FK
}
"Tag" {
Int id PK
String name UK
}
"User" {
Int id PK
String name
String email UK
String password
Role role
DateTime createdAt
}
"_PostToTag" {
String A FK
String B FK
}
"GuestComment" }o--|| "Guest" : guest
"Comment" }o--o| "Comment" : parentComment
"Comment" }o--|| "Post" : post
"Comment" }o--o| "User" : author
"Comment" |o--o| "GuestComment" : guest
"Image" }o--|| "Post" : post
"PostLike" }o--|| "Post" : post
"PostLike" }o--|| "Guest" : guest
"Post" }o--|| "User" : author
"Post" }o--|| "Category" : category
"_PostToTag" }o--|| "Post" : Post
"_PostToTag" }o--|| "Tag" : Tag
비회원 댓글 테이블
Properties
id
: Primary KeynickName
: 닉네임email
: 이메일password
: 비밀번호(해시값)guestId
Foreign Key
작성자(비회원) ID Guest.guestId
비회원 테이블
Properties
id
: Primary KeyguestId
: 비회원 id, 프론트에서 생성
카테고리 테이블
Properties
id
: Primary Keyname
: 카테고리 이름createdAt
: 생성일updatedAt
: 수정일
댓글 테이블
Properties
id
: Primary Keycontent
: 내용createdAt
: 생성일updatedAt
: 수정일parentCommentId
Foreign Key
부모 댓글 ID Comment.id
postId
Foreign Key
게시글 ID Post.id
authorId
Foreign Key
작성자(회원) ID User.id
guestId
Foreign Key
작성자(비회원) ID GuestComment.id
이미지 테이블
Properties
id
: Primary Keyurl
: 이미지 urlpostId
Foreign Key
게시글 ID Post.id
비회원 <=> 게시글 : 다대다, 게시글 좋아요 테이블
Properties
postId
Foreign Key
게시글 ID Post.id
guestId
Foreign Key
비회원 ID Guest.guestId
createdAt
: 좋아요가 눌린 날짜
게시글 테이블
Properties
id
: Primary Keytitle
: 제목content
: 내용createdAt
: 생성일updatedAt
: 수정일prevId
: 이전 게시글 IdnextId
: 다음 게시글 Iddraft
: 초안summary
: 내용 요약authorId
Foreign Key
작성자 ID User.id
categoryId
Foreign Key
작성자 ID Category.id
태그 테이블
Properties
id
: Primary Keyname
: 태그 이름
회원 테이블
Properties
id
: Primary Keyname
: 이름email
: 이메일password
: 비밀번호(해시값)role
: 역할createdAt
: 생성일
Pair relationship table between Post and Tag
Properties
A
:B
: