웹개발에 있어 개발 사이클의 전반적인 흐름을 경험하기 위해 진행한 개인 프로젝트 입니다.
기획, 디자인부터 운영과 배포까지 있어 전체적인 개발기간은 2022.02 ~ 2022.04 입니다.
모바일에서도 사용이 가능하도록 모바일 반응형 디자인을 구현했습니다.
실제 서비스는 헬로디벨로퍼 에서 사용이 가능합니다.
- Visual Studio Code
- DBeaver
- Mysql Workbench
- Figma
- Notion
- Slack
- Typescript
- Nestjs
- Mysql + TypeORM
- AWS EC2
- AWS S3
- AWS ACM
- AWS Route53
- AWS ELB(ALB)
- AWS RDS
- AWS ECR
- Docker
- Passport.js
- JWT
- Typescript
- React
- Recoil
- Styled-components
- AWS S3
- AWS CloudFront
- AWS Route53
- CI/CD : Github Actions
- Markdown Editor/Viewer : Toast UI Editor
- Auth : 인증(로그인, 회원가입, 토큰 등) 관련 모듈
- Boards : 게시글(작성, 삭제 등) 관련 모듈
- User: 사용자(프로필, 활동내역 등) 관련 모듈
graph TD;
APP-->Auth;
APP-->Boards;
APP-->Users;
Auth-->Email
Auth-->Util
Boards-->Util
Boards-->AWS
Boards-->Comments;
Users-->AWS
API 문서 URL : Hello Developer API Docs.
서버에서 치명적인 500대의 HTTP 에러가 발생한경우에 로깅하도록 처리했습니다.
에디터를 통해 이미지 업로드 및 DB에 저장시 아래와 같은 상황이 발생했습니다.
- 마크다운 에디터에서 이미지 삽입시 인코딩되어 본문의 길이가 길어지는 상황
- 약 4kb의 이미지 삽입시 약 5100자의 문자열이 삽입
- 본문의 길이가 늘어날수록 DB에서 차지하는 공간이 커지게되며, 삽입/삭제 등 데이터의 크기가 늘어날수록 더 많은양의 네트워크 트래픽을 요구
이미지 업로드 이벤트를 커스텀하여 AWS S3에 업로드 및 이미지 URL을 반환하여 업로드를 최적화 했습니다.
해결과정은 Tistory 블로그에 정리했습니다.
기존 게시글을 작성하는 DB Column 생성시 UUID 형식의 랜덤한 게시글 ID가 생성되는 형식
하지만 이미지 업로드시에 게시글이 DB에 저장되는 시점보다 이전에 업로드 이벤트가 발생
위와 같은 문제점을 아래와 같이 방법으로 개선하였습니다.
- 게시글 생성시 클라이언트에서 임시 게시글 ID 생성
- 임시 게시글 ID를 기준으로 S3에 /temp-board-id/image.png 형식으로 업로드
- 게시글이 저장되는 시점에 게시글 DTO에 UUID 문자열을 ID로 추가
- 게시글이 저장되는 동시에 S3에 존재하는 temp-board-id를 실제 게시글 ID로 치환
sequenceDiagram
participant Client
participant Server
participant AWS
Client->>Server: Image Upload Req
Server->>AWS: Image Upload in S3
Server->>Client: Return Image URL
Client->>Server: Save Board Req
Server->>AWS: Change Folder Name
nodemailer + gmail을 사용하여 이메일 인증을 구현중 아래 문제가 발생했습니다.
- 2022.05.30 이후 OAuth2.0 미사용시 Gmail API 사용 불가
- 공식문서에서는 OAuth 사용방법과 관련된 자세한 방법이 나와있지 않았음
위 제약사항으로 인해 Gmail API의 OAuth와 nodemailer를 사용하여 인증메일 발송기능을 구현했습니다.
해결과정은 Tistory 블로그에 정리했습니다.
기본적으로 Access Token의 경우 localStorage등 클라이언트(브라우저)의 저장공간에서 보관합니다.
이에 따라서 수명이 길면 탈취당했을때 문제가 발생할 수 있습니다.
JWT Refresh Token을 활용하여 유저의 사용경험이 불편하지 않도록 개선했습니다.
- 토큰의 유효시간의 경우 Access Token은 15분, Refresh Token은 10일로 지정
- Refresh Token은 httpOnly, Secure 옵션으로 쿠키에 저장
- 서버에서 401 Unauthorized 에러 반환시 Refresh Token을 활용한 재요청 및 Access Token 재발급
res.cookie('refreshToken', refreshToken, { httpOnly: true, path: '/', secure: true });
게시글 상세보기를 누르게되면 조회수를 증가하기 위해 API를 호출하게 됩니다.
이때 이미 조회한 게시물도 계속 API 호출이 들어오게 됩니다.
사용자가 악의적으로 새로고침 테러를 하는것을 방지하기 위해 쿠키를 설정하여 해결했습니다.
- 게시글 상세보기 페이지 접속시 24시간동안 유지되는 쿠키 생성
- 게시글 상세보기 페이지 접속시 쿠키 여부에 따라 조회수 호출여부 결정
게시글 목록 또는 검색기능 사용시 DB에 존재하는 모든 데이터를 한번에 응답받습니다.
약 1000개의 데이터가 존재할 경우 렌더링시 매우 버벅이게 되며 유저에게는 안좋은 경험으로 와닿습니다.
현재 생각중인 해결방안은 아래와 같습니다.
- 클라이언트 : 무한스크롤을 구현하여 특정 시점에 서버에 데이터 요청
- 서버 : 쿼리스트링을 통해 반환할 데이터의 범위를 받고, 데이터베이스 측에서 필터링을 통해 데이터 반환
@ApiOperation({ summary: '회원가입 API' })
@ApiCreatedResponse({
description: `회원가입 성공시 201 코드와 생성된 사용자의 아이디 반환`,
schema: {
type: 'object',
properties: { userId: { type: 'string' } },
},
})
@ApiBadRequestResponse({
description: `
이메일 형식이 올바르지 않을경우 - invalid_email
비밀번호 형식이 올바르지 않을경우 - invalid_password
닉네임 형식이 올바르지 않을경우 - invalid_nickname
중복된 이메일인 경우 - exist_email
중복된 닉네임인 경우 - exist_nickname`,
schema: {
type: 'object',
properties: {
statusCode: { type: 'number' },
message: {
example: 'invalid_email, invalid_password, invalid_nickname, exist_email, exist_nickname',
},
},
},
})
@UsePipes(ValidationPipe)
@Post('register')
async register(@Body() registerDto: RegisterDto) {
const userId = await this.authService.register(registerDto);
return { userId };
}
@ApiRegister('회원가입 API')
@UsePipes(ValidationPipe)
@Post('register')
async register(@Body() registerDto: RegisterDto) {
const userId = await this.authService.register(registerDto);
return { userId };
}
처음 도입하고 경험한 기술과 개념들이 들이 매우 많았습니다.
TDD 부터 시작해서 CI/CD, AWS, 새로운 프레임워크/라이브러리 등..
새로운걸 배울때마다 계속해서 기록하고 꾸준하게 노력했습니다.
처음 마주하는 문제더라도 꾸준히 노력하면 해결할수 있다는 자신감을 얻을 수 있었습니다.