NestJS Course
Search

1. 백엔드 NestJS+내장 Express 컨테이너화 및 실행
1.1 Dockerfile 및 .dockerignore 작성
•
MP_Project/Back-end/Dockerfile 생성
◦
확장자 없음, 아래 내용 그대로 작성
•
MP_Project/Back-end/.dockerignore 생성
◦
불필요 파일 제거 이미지 생성을 위한 제외
[Docker] Docker VM 활용 통합 배포와 CI/CD 파이프라인 구축

NestJS를 통한 백엔드 서버 구축
NestJS Tutorials
NestJS R&D
… 계속하여 추가 기능 개발
[NestJS] 0. NestJS 들어가기

1. NestJS의 공식 문서 살펴보기
1.1 소개
NestJS?
Nest(NestJS)는 효율적이고 확장 가능한 Node.js 서버 사이드 애플리케이션을 구축하기 위한 프레임워크입니다. 점진적 JavaScript를 사용하고 TypeScript 로 빌드되어 완벽하게 지원하며 (그래도 개발자는 순수 JavaScript로 코딩할 수 있음) OOP(객체 지향 프로그래밍), FP(함수형 프로그래밍), FRP(함수형 반응형 프로그래밍)의 요소를 결합합니다.
Nest는 이러한 일반적인 Node.js 프레임워크(Express/Fastify)보다 더 높은 수준의 추상화를 제공하지만, API를 개발자에게 직접 노출합니다. 이를 통해 개발자는 기본 플랫폼에서 사용할 수 있는 수많은 타사 모듈을 자유롭게 사용할 수 있습니다.
[NestJS] 1. NestJS 소개

1. 프로젝트 기본구조
1.1 프로젝트 생성
프로젝트 생성 하기
•
nestjs/cli 를 설치 했으며 환경변수 설정까지 완료 했으므로 터미널을 통해서 다음과 같은 명령어로 기본 프로젝트 구조를 생성 할 수 있게 되었다.
•
주의할 것은 CLI 명령어가 입력되는 현재 경로를 잘 확인해야 한다.
•
VSCode 또한 에디터 하단에 터미널을 이용 할 수 있으므로 해당 부분에 CLI 명령어를 통해서 프로젝트를 생성 하면 된다.
기본적인 커맨드라인 명령어
•
/projects/ 경로로 이동해서 다음과 같은 프로젝트 생성 명령어를 입력
nest new nestjs-board-app
Shell
복사


•
이처럼 CLI 명령어로 Nest프로젝트가 생성되면 IDE로 사용할 VSCode로 해당 폴더를 연다.

•
아래와 같은 프로젝트 기본 요소들이 생성 되어 있는 것을 확인

그 외 IDE를 사용하는경우 ex)IntelliJ IDEA 등
[NestJS] 2. NestJS 기본 요소

1. 게시글 엔터티 설계
엔터티란?
•
엔터티(Entity)는 업무에 필요하고 유용한 정보를 저장하고 관리하기 위한 "어떤 것(Thing)"
•
"어떤 것"이라고 부르는 것처럼 엔터티는 추상적인 의미를 가지며 학교나 학생처럼 현실 세계에서 눈에 보이는 개념일 수도 있고 주문이나 결제처럼 눈에 보이지 않는 개념일 수도 있다.
•
엔터티는 쉽게 말해 실체, 객체라고 생각할 수 있다.
◦
엔터티는 어떠한 객체이며 속성을 갖는다.
▪
객체가 가지고 있는 정보, 특징은 속성이 된다.
▪
더이상 분리 되지 않는 최소 데이터 단위를 속성이라 한다.(ex:이름, 제목, 나이, 키 등)
◦
두개 또는 그 이상의 엔터티들은 관계를 갖는다.
▪
다른 엔터티와의 존재에 의한 관계(ex:직원엔터티는 부서엔터티에 소속된다. 정적인 관계)
▪
다른 엔터티와의 행위에 의한 관계(ex:고객엔터티는 상품엔터티를 주문한다. 동적인 관계)
게시글이 가지게되는 속성은 어떤것들이 있을까?
•
id : 게시글의 번호(고유번호) - 숫자
•
author : 게시글 작성자 - 문자
•
title : 게시글의 제목 - 문자
•
contents : 게시글의 내용 - 문자
•
status : 공개 상태 - 공개 or 비공개
board.entity.ts
•
nestjs/cli 명령어로는 entity 파일의 자동 generation을 지원하지 않는다.
•
따라서 수동으로 파일을 생성해준다.
•
아래와 같이 위 설계한 속성들을 Board 클래스에 정의해준다.
◦
status는 BoardStatus라는 ENUM 클래스를 사용
import { BoardStatus } from "./board-status.enum";
export class Board {
id: number;
author: string;
title: string;
contents: string;
status: BoardStatus;
}
TypeScript
복사
[NestJS] 3. NestJS CRUD Operation 개발

1. 유효성 검사에 대하여
올바르지 않은 데이터를 걸러내고 보안을 유지하기 위한 기본 검사

•
유효성 검사와 그에 대한 예외 처리는 백엔드와 프론트엔드 모두에서 중요하게 다뤄져야 한다.
•
→ 이는 애플리케이션의 무결성과 보안성을 유지하는 데 필수적
프론트엔드에서의 유효성 검사
•
사용자 경험 (UX) 향상
◦
사용자가 잘못된 데이터를 입력했을 때 즉시 피드백을 제공하여, 오류를 실시간으로 수정 → UX 향상
•
비용 절감
◦
잘못된 데이터가 서버로 전송되는 것을 막아, 불필요한 서버 요청 최소화
•
기본적인 보안
◦
가장 일반적인 입력 오류를 초기에 차단
◦
악의적인 사용자가 기본적인 유효성 검사를 우회하는 것을 차단
◦
다만, 프론트엔드 유효성 검사는 클라이언트 측에서 실행되므로, 보안 목적으로만 의존해서는 안됨
백엔드에서의 유효성 검사
•
데이터 무결성
◦
데이터베이스에 저장되기 전 데이터를 최종적으로 확인하여 데이터베이스의 무결성을 보장
▪
예를 들어, 이메일 형식이 맞지 않는 데이터를 저장하는 것, null 값 등
•
보안성 강화
◦
악의적인 사용자가 프론트엔드에서 유효성 검사를 우회할 수 있기 때문에, 백엔드에서의 검사는 필수
◦
모든 입력 데이터가 서버에서 한 번 더 검증되어야 함
◦
SQL 인젝션, XSS 등 다양한 보안 취약점을 방어
•
시스템 안정성
◦
예상치 못한 입력으로 인한 시스템 오류를 방지
◦
예를 들어, 백엔드에서 예상치 못한 형식의 데이터를 처리하지 못해 서버의 중단 등 방지
[NestJS] 4. Pipe를 통한 유효성 체크와 예외처리

1. 데이터베이스 환경 구축
1.1 데이터베이스란?
데이터베이스(Database)는 데이터를 체계적으로 저장하고 관리하는 시스템, 데이터의 집합을 이야기 한다.
•
데이터를 효율적으로 저장, 검색, 업데이트, 삭제할 수 있는 공간
•
데이터베이스는 다양한 응용 프로그램에서 필수적인 역할을 하며, 데이터를 안전하게 보관하고 필요한 사람이나 시스템이 쉽게 접근할 수 있도록 함
DBMS란?
DBMS(Database Management System)
•
데이터베이스를 관리, 운영 해주는 소프트웨어
•
MySQL, PostgreSQL, Oracle, MongoDB 등이 대표적인 DBMS
[NestJS] 5. DB와 TypeORM 연동

1. Service 계층 리팩토링
임시DB에서 MySQL, TypeORM을 적용했으므로 Service 계층 코드를 전체적으로 수정한다.
•
임시 비활성화 주석 부분을 모두 삭제해도 무방하다 전반적인 코드가 async/await 문법으로 수정되었다.
◦
CREATE, UPDATE 기능은 save() 메서드를 사용했다.
▪
create() 메서드는 저장 전 DTO객체를 Entity 클래스를 참조한 인스턴스를 생성한다.
▪
PATCH 메서드의 일부 수정 기능은 update() 메서드를 사용했다.
◦
READ 중 전체 조회 기능은 find() 메서드를 사용했다.
◦
READ 중 특정 조회 기능은 findOneBy(), findBy() 메서드 사용했다.
▪
findOneOrFail() 처럼 예외 처리를 함께 할 수 있는 메서드도 존재
◦
DELETE 기능은 remove(), delete() 메서드를 사용할 수 있다.
Cascade 에 대해 인지하고 사용해야 한다.
•
boards.service.ts
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Board } from './boards.entity';
import { BoardStatus } from './boards-status.enum';
import { CreateBoardDto } from './dto/create-board.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UpdateBoardDto } from './dto/update-board.dto';
@Injectable()
export class BoardsService {
// Repository 계층 DI
constructor(
@InjectRepository(Board)
private boardRepository : Repository<Board>
){}
// 게시글 조회 기능
async getAllBoards(): Promise<Board[]> {
const foundBoards = await this.boardRepository.find();
return foundBoards;
}
// 특정 게시글 조회 기능
async getBoardDetailById(id: number): Promise<Board> {
const foundBoard = await this.boardRepository.findOneBy({ id: id });
if(!foundBoard) {
throw new NotFoundException(`Board with ID ${id} not found`);
}
return foundBoard;
}
// 키워드(작성자)로 검색한 게시글 조회 기능
async getBoardsByKeyword(author: string): Promise<Board[]> {
if (!author) {
throw new BadRequestException('Author keyword must be provided');
}
const foundBoards = await this.boardRepository.findBy({ author: author })
if (foundBoards.length === 0) {
throw new NotFoundException(`No boards found for author: ${author}`);
}
return foundBoards;
}
// 게시글 작성 기능
async createBoard(createBoardDto: CreateBoardDto): Promise<Board> {
const { author, title, contents } = createBoardDto;
if (!author || !title || !contents) {
throw new BadRequestException('Author, title, and contents must be provided');
}
const newBoard: Board = {
id: 0, // 임시 초기화
author, // author: createBoardDto.author
title,
contents,
status: BoardStatus.PUBLIC
};
const createdBoard = await this.boardRepository.save(newBoard);
return createdBoard;
}
// 특정 번호의 게시글 수정
async updateBoardById(id: number, updateBoardDto: UpdateBoardDto): Promise<Board> {
const foundBoard = await this.getBoardDetailById(id);
const { title, contents } = updateBoardDto;
if (!title || !contents) {
throw new BadRequestException('Title and contents must be provided');
}
foundBoard.title = title;
foundBoard.contents = contents;
const updatedBoard = await this.boardRepository.save(foundBoard)
return updatedBoard;
}
// 특정 번호의 게시글 일부 수정
async updateBoardStatusById(id: number, status: BoardStatus): Promise<void> {
const result = await this.boardRepository.update(id, { status });
if (result.affected === 0) {
throw new NotFoundException(`Board with ID ${id} not found`);
}
}
// 게시글 삭제 기능
async deleteBoardById(id: number): Promise<void> {
const foundBoard = await this.getBoardDetailById(id);
await this.boardRepository.delete(foundBoard);
}
}
TypeScript
복사
2. Controller 계층 리팩토링
[NestJS] 6. DB+TypeORM → CRUD Operation 연동 리팩토링 |
중요

논리적 문제 해결 프로세스
문제 해결 프로세스에 따른 과정


•
각 팀 또는 솔루션마다 단계별 문제 해결 모델들을 보여주고 있다.
•
문제 해결 과정에서 공통적으로 필수적이라 생각되는 부분은 아래와 같이 구분 해보았다.
◦
문제의 인식과 원인 분석
◦
문제 해결 방안 탐색과 타당성 검토
◦
문제 해결 실행
◦
해결된 문제의 테스트 또는 검증
◦
과정에서 발생하는 문제에 대한 트러블 슈팅 또는 추가 검토, 정리
•
개발중인 프로젝트에서의 문제를 발견하고 해결하는 과정을 가정하여 진행하고자 한다.
[NestJS] 8. 엔터티의 연관 관계(Relationships) feat. 문제 인식과 해결

1. Log란?
Log란 시스템이나 애플리케이션이 작동하면서 발생하는 다양한 사건이나 상태 변화를 기록한 정보
•
로그는 주로 텍스트 파일 또는 데이터베이스에 저장
•
시스템이 어떻게 동작하고 있는지, 어떤 오류가 발생했는지, 또는 사용자가 어떤 작업을 수행했는지 추적 하는 목적
•
로그의 목적
◦
투명성: 로그는 시스템의 내부 동작을 투명하게 드러내며, 문제 발생 시 그 원인을 파악하는 데 중요한 자료를 제공
◦
문제 해결 및 디버깅: 로그를 통해 개발자나 시스템 관리자들은 문제 발생 시 그 원인을 신속히 찾고 해결
◦
보안: 보안 관련 로그는 시스템이 해킹 당했거나 보안 위협이 발생했을 때 이를 탐지하는 데 중요한 역할
◦
감사와 규제 준수: 특히 금융, 의료 등 규제가 엄격한 산업에서는 로그를 통해 각종 규제 요구 사항을 준수했음을 증명
Logging Level
•
로그 메시지의 중요도나 심각도를 분류하는 개념
◦
TRACE (가장 낮은 수준)
▪
설명: 주로 애플리케이션의 매우 세부적인 실행 흐름을 추적하는 데 사용되며, 변수의 값이나 함수 호출의 세부 정보 등을 기록
▪
사용 예시: 함수의 진입과 종료, 특정 루프의 각 반복에서 발생하는 상세한 처리 내용
▪
사용 시기: 거의 모든 이벤트를 기록해야 하는 경우, 디버깅을 위해 매우 세밀한 정보를 필요로 할 때
◦
DEBUG
▪
설명: 개발 중 문제를 해결하기 위해 사용. 시스템의 내부 상태, 변수 값, 문제를 해결하는 데 필요한 진단 정보 등을 포함
▪
사용 예시: 특정 조건에서만 발생하는 버그를 추적할 때, 코드의 흐름을 확인하고자 할 때
▪
사용 시기: 개발 환경이나 테스트 환경에서 디버깅을 위해 사용
◦
INFO
▪
설명: 시스템의 정상적인 동작에 대한 일반 정보를 기록. 일반적인 실행 흐름을 설명하고, 시스템 상태나 주요 이벤트를 추적
▪
사용 예시: 애플리케이션이 시작되었을 때, 중요한 사용자 활동(로그인, 로그아웃) 등이 발생했을 때
▪
사용 시기: 시스템의 기본 운영 상태를 모니터링할 때, 정상적인 동작을 기록할 때
◦
WARN
▪
설명: 주의가 필요하지만, 즉각적인 문제가 발생하지는 않은 상황을 기록 잠재적인 문제나 향후 시스템 동작에 영향을 줄 수 있는 상황을 알리는 데 사용
▪
사용 예시: 오래된 API가 사용되었을 때, 디스크 공간이 줄어들고 있는 상황 등
▪
사용 시기: 잠재적인 문제를 감지하고, 관리자가 주의를 기울여야 할 때
◦
ERROR
▪
설명: 애플리케이션에서 오류가 발생했을 때 기록. 시스템이 정상적으로 동작하지 않으며, 문제를 해결하기 위한 조치가 필요
▪
사용 예시: 데이터베이스 연결 실패, 예외 처리에서 발생한 오류 등
▪
사용 시기: 시스템의 문제가 발생했을 때, 해당 문제를 추적하고 해결하기 위해 사용
◦
FATAL (가장 높은 수준)
▪
설명: 치명적인 오류로, 애플리케이션이 더 이상 실행될 수 없는 상황을 기록. 즉각적인 조치가 필요하며, 시스템이 강제로 종료될 수도 있음
▪
사용 예시: 중요한 데이터 손실, 애플리케이션의 중대한 충돌 등.
▪
사용 시기: 시스템의 즉각적인 복구가 필요한 경우, 심각한 문제로 인해 프로그램이 종료될 때.
[NestJS] 9. 로깅과 AOP

1. 현재까지 진행된 상태에서 짚어 볼 수 있는 문제점
1.1 컨벤션(Convention) 이란?
명명규칙을 기획 초기에 수립하는 것의 중요성을 확인하고자 한다.
•
컨벤션(Convention)은 코드 작성, 명명 규칙, 폴더 구조, 파일명 등 프로젝트 내에서 개발자들이 공통적으로 따르는 규칙이나 가이드라인을 의미
•
이런 규칙을 수립하는 것을 컨벤션을 수립하다 라는 표현을 쓰기도 한다.
•
컨벤션은 문서화하여 프로젝트의 모든 개발자가 참고할 수 있도록 하는 것이 좋음, 보통은 프로젝트의 README.md 파일이나 프로젝트 SA페이지 또는 팀 블로그 등 페이지로 정리
•
실전 문서화 예시


컨벤션의 목적
•
일관성 유지: 코드베이스 전체에서 일관된 스타일과 구조를 유지
•
가독성 향상: 명확한 규칙에 따라 작성된 코드는 이해하기 쉽고, 다른 개발자들이 코드를 확인하는 유지보수성 향상
•
협업 효율성 증가: 팀원 간의 코드 스타일이 통일되면 협업 시 불필요한 충돌이 줄어들고, 코드 리뷰의 편의성 향상
•
버그 예방: 일관된 규칙을 따르면 실수나 버그를 쉽게 찾아내고 빠른 픽스가 가능
컨벤션의 종류
•
코딩 스타일 컨벤션:
◦
들여쓰기, 공백, 줄바꿈, 중괄호 배치 등 코드 스타일에 대한 규칙
◦
예: 2-space 들여쓰기 vs 4-space 들여쓰기, 공백 한 줄 삽입 등
•
명명 규칙 컨벤션:
◦
변수, 함수, 클래스, 파일명 등을 어떻게 명명할지에 대한 규칙
◦
예: camelCase vs snake_case, PascalCase, Kebab-Case 사용 등
•
폴더 구조 컨벤션:
◦
프로젝트의 폴더 구조와 파일 배치에 대한 규칙
◦
예: 기능별로 폴더를 나누기, 특정 폴더에 특정 타입의 파일 배치 등
•
프레임워크 컨벤션:
◦
특정 프레임워크에서 권장하는 규칙
◦
예: NestJS에서 컨트롤러 파일은 .controller.ts로, 서비스 파일은 .service.ts로 명명하기 등
•
커밋 컨벤션 및 PR 컨벤션
◦
일관된 방식으로 커밋 메시지, PR을 작성하고 관리하기 위한 규칙

◦
예: 커밋 컨벤션 (git config 를 통한 commit_messege_template.md 설정)
# <타입> : <제목> 형식으로 작성하세요
################
# 타입 설정하기
## feature : 새로운 기능 추가
## fix : 버그 수정
## docs : 문서 수정
## test : 테스트 코드 추가
## refactor : 코드 리팩토링
## style : 코드 의미에 영향을 주지 않는 변경사항
## chore : 빌드 부분 혹은 패키지 매니저 수정사항
# 제목 설정하기
## 제목 작성 후 공백 한줄을 포함해야, 제목과 본문이 구별됨
## 제목 첫 글자는 대문자로 작성, 마침표를 사용하지 않음
## 제목은 명령문으로 사용, 과거형을 사용하지 않음
## 제목은 50글자로 제한
################
##### 제목을 아랫줄에 작성하세요 #####
################
# 본문 설정
## 본문 작성 후 공백 한줄을 포함해야, 본문과 Resolves가 구별됨
## 본문의 각 행은 72글자로 제한
## 본문은 "왜"와 "무엇을"위주로 작성
## 본문은 행으로 구분되어야 함
## 본문의 내용은 *으로 시작함
################
##### 본문(추가 설명)을 아랫줄에 작성하세요 #####
################
# Resolves 설정
## Resolves 작성 후 공백 한줄을 포함해야, Resolves와 See also가 구별됨
## Resolves의 내용도 *으로 시작함
## 해결한 이슈는 닫을 수 있도록 함.
# 이슈 종료 방법
## '키워드 #이슈번호'
# issue 종료 키워드 (github)
## * close - 일반 개발 이슈
## * closes
## * closed
## * fix - 버그 fix 이슈
## * fixed
## * resolve - 문의 요청사항 이슈
## * resolves
## * resolved
################
##### Resolves를 작성하세요 (생략 가능) #####
################
# See also 설정
## 연관된 이슈의 경우 이슈 번호와 연관 이슈 내용을 입력
## See also의 내용도 *으로 시작함
################
##### See also를 작성하세요 (생략 가능) #####
################
# Remember me ~ Commit Message 규칙
## 1. 제목과 본문을 빈 행으로 구분한다.
## 2. 제목을 50글자 내로 제한
## 3. 제목 첫 글자는 대문자로 작성
## 4. 제목 끝에 마침표 넣지 않기
## 5. 제목은 명령문으로 사용하되, 과거형을 사용하지 않는다.
## 6. 본문의 각 행은 72글자 내로 제한
## 7. 어떻게 보다는 무엇과 왜를 설명
################
Markup
복사

◦
예: PR 컨벤션(.github PULL_REQUEST_TEMPLATE.md) 템플릿 예시
### PR 타입(하나 이상의 PR 타입을 선택해주세요)
- [X] 기능 추가
- [ ] 기능 삭제
- [ ] 버그 수정
- [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트
### 반영 브랜치
### 변경 사항
### 테스트 결과
Markup
복사

[NestJS] 10. 프로젝트 문제 인식과 컨벤션 리팩토링, 트러블 슈팅

1. 파일 업로딩과 스트리밍
1.1 뷰 템플릿 엔진
•
목표: NestJS에서 뷰 템플릿 엔진을 사용하여 서버 사이드 렌더링(SSR) 구현
•
서버 측에서 HTML 템플릿을 렌더링하여 클라이언트에 동적인 콘텐츠를 제공
[NestJS] 11. 심화 목표 키워드

1. 다음 우편번호 API 서비스 활용하기
해당 부분은 프론트엔드에서 처리할 로직이 대부분이다.
•
하지만 해당 API 기능을 사용하게 된다면 서버에서도 주소 관련 데이터를 받을 준비를 해야 한다.

반환되는 데이터 확인하기
•
기본적으로 이런 외부 API의 경우 이미 정해진 변수명, 타입 등으로 데이터를 JSON 형태로 내보내준다.
•
Kakao 주소검색 API 에서 반환해주는 데이터의 예시는 다음과 같다.

•
여기서 중요한 Key, Value는 다음과 같다.
◦
zonecode : 우편번호 (* 구버전에서는 postcode였으나 변경됨)
◦
address : 주소 (한글주소)
여기까지는 주소 검색을 통해서 제공 될 부분
•
주소에서 세부 주소(동호수 등)이 필요하기 때문에 detailAddress 입력 필드가 추가로 필요하다.
[NestJS] 12. Kakao 다음 우편번호 API 주소 입력 추가

1. 페이징 기능 추가
article.service.ts
•
페이징 처리를 위해서는 쿼리스트링(Param)으로 페이지와 게시글 수를 받아와야 한다.
•
getPaginatedArticles(page: number, limit: number) 메서드 명으로 위 두개의 파라미터를 받아온다.
◦
page : 현재 페이지 번호(1,2,3,…)
◦
limit : 한 개의 페이지당 보여줄 게시글의 수
◦
skip : (page - 1) * limit 를 통해 이전 페이지에서 가져왔던 게시글을 건너뛰기 위한 변수
▪
ex) 2페이지로 접근하면, (2-1) * 10 = 9개 를 건너 뜀
•
TypeORM의 find() 메서드의 페이징 인터페이스 활용
◦
find() 메서드는 skip과 take를 함께 사용하면 페이징이 구현됨


...
import { UserRole } from 'src/user/user-role.enum';
@Injectable()
export class ArticleService {
private readonly logger = new Logger(ArticleService.name); // Logger 인스턴스 생성
constructor(
@InjectRepository(Article)
private articleRepository: Repository<Article>
){}
...
// 전체 게시글 조회
...
// 페이징 추가 게시글 조회 기능
async getPaginatedArticles(page: number, limit: number): Promise<Article[]> {
this.logger.verbose(`Retrieving paginated articles: page ${page}, limit ${limit}`);
const skip: number = (page - 1) * limit;
const articles = await this.articleRepository.find({
skip,
take: limit,
order: { createdAt: 'DESC' } // 내림차순
});
this.logger.verbose(`Paginated articles retrieved successfully`);
return articles;
}
// 나의 게시글 조회
...
}
TypeScript
복사
article.controller.ts
•
getPaginatedArticles( @Query('page') page: number = 1, @Query('limit') limit: number = 10 ) 라는 메서드에 page, limit 두개의 파라미터를 쿼리스트링으로 받아온다.
◦
특별한 입력이 없는 경우에도 기본값으로 1페이지, 10개 게시글을 볼 수 있도록 기본값 설정
•
Service 계층의 getPaginatedArticles() 메서드에 인수로 page, limit을 전달하며 호출
•
게시글 배열 결과를 DTO로 변환하고, 메시지, 상태코드 등과 함께 ApiResponseDto에 맞춰 클라이언트로 응답하게 된다.
...
import { ArticleResponseDto } from './dto/article-response.dto';
@Controller('api/articles')
@UseGuards(AuthGuard('jwt'), RolesGuard) // JWT 인증과 role 커스텀 가드를 적용
export class ArticleController {
private readonly logger = new Logger(ArticleController.name); // Logger 인스턴스 생성
// 생성자 주입(DI)
constructor(private articleService: ArticleService){}
...
// 전체 게시글 조회 기능
...
// 페이징 처리된 게시글 조회
@Get('/paginated')
async getPaginatedArticles( @Query('page') page: number = 1, @Query('limit') limit: number = 10 ): Promise<ApiResponse<ArticleResponseDto[]>> {
this.logger.verbose(`Retrieving paginated articles: page ${page}, limit ${limit}`);
const articles = await this.articleService.getPaginatedArticles(page, limit);
const articleDtos = articles.map(article => new ArticleResponseDto(article));
this.logger.verbose(`Paginated articles retrieved successfully`);
return new ApiResponse(true, 200, 'Paginated articles retrieved successfully', articleDtos);
}
// 나의 게시글 조회 기능
...
}
TypeScript
복사
•
POSTMAN을 통한 테스트
◦
1페이지를 조회하는 경우

▪
29~20번까지 10개 게시글 조회

◦
2페이지를 조회하는 경우

▪
첫페이지(29~20번) 이후인 19~10번까지 10개 게시글 조회

[NestJS] 13. 게시글 페이징(Pagenation)과 무한스크롤(Infinity Scroll)

1. OAuth란?
OAuth 2.0(Open Authorization)
•
외부 애플리케이션이 사용자 계정에 대한 제한된 액세스를 허용할 수 있도록 하는 인증 표준
•
제3자가 사용자의 비밀번호 없이 안전하게 사용자 정보를 액세스하도록 허용하는 메커니즘
◦
간단히 카카오, 구글, 페이스북 처럼 인증 신뢰도가 높은 기업이 인증을 대신 제공해주는 방식
◦
위 인증 제공자(OAuth Provider) 기업들이 클라이언트(다른 웹 서비스)에 자신들의 회원 계정을 이용 할 수 있도록 함
•
이를 통해 사용자는 여러 애플리케이션에서 하나의 외부 서비스(소셜 로그인)를 사용해 쉽게 인증할 수 있는 편리함을 제공 받게 됨
•
사용자의 비밀번호를 공유하지 않고도 애플리케이션이 사용자 정보에 접근하게 되어 안정성을 제공 할 수 있음
OAuth1.0? 2.0?
•
OAuth 1.0: 복잡한 서명 기반 인증 방식을 사용했으며, 클라이언트와 서버 간의 통신에서 많은 보안 절차를 요구했던 기술
•
OAuth 2.0: 더 간단한 토큰 기반 인증 방식을 사용하며, 다양한 권한 부여 흐름(Grant Types)을 지원해서 웹, 모바일 앱, 서버 간 통신 등 다양한 시나리오에 쉽게 적용할 수 있게 된 현재의 기술
◦
2012년에 표준으로 발표되어 현재 OAuth라 하면 OAuth2.0을 말함
[NestJS] 14. Kakao API 소셜 로그인 기능 구현(OAuth)

1. Client Side Rendering이란?
Client-Side Rendering
•
클라이언트 사이드 렌더링(CSR)은 웹 페이지의 콘텐츠를 서버에서 전달하는 대신, 웹 브라우저(클라이언트)에서 직접 렌더링하는 방식
•
웹 페이지의 콘텐츠를 서버에서 HTML을 완전히 구성하지 않고, JavaScript를 통해 웹 브라우저(클라이언트)에서 콘텐츠를 동적으로 렌더링하는 방식
•
프론트엔드 서버는 주로 JavaScript, CSS, HTML의 기본 템플릿을 전달하고, 브라우저에서 JavaScript가 데이터를 가져와 화면을 구성
•
Angular와 같은 프론트엔드 애플리케이션을 별도의 Express 웹서버에서 호스팅하는 것은 관례적인 패턴이고 nginx 등 다양한 웹서버를 고려 할 수 있다.
◦
Express는 정적 파일(빌드된 HTML, JS, CSS)을 클라이언트에 서빙하는 역할
•
ng serve 명령어는 개발 환경에서 사용
◦
실제 배포 시에는 빌드된 정적 파일들을 별도의 서버(예: Express 서버 또는 CDN)에서 서빙
•
실제로 배포된 상태에서는 독립된 서버(정적 서버 또는 백엔드 서버)에서 정적 파일을 클라이언트로 전송하는 방식이 사용
Server-Side Rendering은?
•
서버 사이드 렌더링(SSR)은 서버에서 페이지를 미리 렌더링하여 완전한 HTML 파일을 클라이언트에 전달하는 방식
•
서버는 요청을 받을 때마다 필요한 데이터를 조회하고, 템플릿 엔진(예: EJS, Handlebars, Pug)을 통해 HTML을 생성하여 클라이언트로 응답
•
SSR은 백엔드에서 뷰 템플릿 엔진을 사용하는 경우
•
프론트엔드 프레임워크(예: Next.js, Nuxt.js, Angular) 등에서도 서버에서 HTML을 생성하여 전송하는 방식을 포함
•
프론트엔드 정적 페이지를 백엔드 서버에서 정적 파일로 서빙하는 것은 SSR과 구별되며. 정적 파일 서빙은 정적 콘텐츠를 클라이언트로 단순히 전달하는 것이며, 렌더링 과정이 서버에서 실시간으로 이루어지는 SSR과는 다른 개념
•
프론트엔드 프레임워크를 사용하면서 프론트엔드 정적페이지를 백엔드 서버에서 정적파일로딩에 올리는 경우에도 서버사이드 렌더링으로 포함(백엔드 실행 시 포함된 프론트엔드 빌드 파일 자동 실행인 경우)
Client-Side Rendering을 사용하는 이유?
•
CSR은 웹 페이지의 부분적인 업데이트와 동적 렌더링을 지원
◦
이는 jQuery를 통해 웹 페이지의 동적 렌더링을 경험해본 경우(타이핑에 검색어 자동 추적 등) 사용자 경험이 대폭 상승 하는 것을 알 수 있음
•
빠른 페이지 전환
◦
클라이언트에서 필요한 데이터만 서버로부터 가져와 화면을 업데이트
•
CSR은 서버에서 렌더링을 하지 않기 때문에 서버의 부하를 줄이고, 서버의 스케일링을 더 유연하게 관리
•
프론트엔드 개발의 유연성
◦
백엔드, 프론트엔드 파트별 개발의 독립성을 가질 수 있음
◦
프론트엔드 애플리케이션에서 복잡한 비즈니스 로직을 구현하는데 집중 할 수 있음
[NestJS] 15. CSR과 프론트엔드의 API 요청(Fetch API / HttpClient API)

1. File Input/Output이란?
FIle I/O
•
파일 I/O는 서버와 파일 시스템 간의 데이터 흐름을 처리하는 중요한 기능 중 하나
•
특히 파일 업로드/다운로드, 로그 기록, 데이터 백업과 같은 작업에서 많이 사용
파일 시스템에서의 I/O와 HTTP 환경에서의 I/O는 다르다.
•
파일 시스템 I/O:
◦
직접적으로 디스크의 파일을 읽고 쓰는 작업.
◦
파일 경로와 파일 시스템의 상태에 따라 작업이 이루어짐.
◦
메모리와 디스크의 물리적 위치에 의존.
관련 모듈
•
HTTP I/O:
◦
네트워크를 통한 클라이언트와 서버 간의 데이터 전송.
◦
multipart/form-data 형식의 요청 본문을 처리하여 파일 업로드 및 다운로드를 수행.
◦
스트리밍 처리와 같은 네트워크 효율성을 고려한 데이터 처리 방식.
[NestJS] 16. 파일 입출력(I/O) 기능 구현

1. MVP 기능 구현 추가 계획
현재 페이지 구성은 일부만을 HttpClient API 요청 확인을 위해서 임시 페이지처럼 구성 했었다.
•
따라서 대부분의 테스트는 POSTMAN으로 API 확인이 대부분이었다.
•
이제 실제 프론트엔드만으로도 사용 할 수 있도록 게시글 작성, 조회, 검색, 회원 정보 수정 등 프론트엔드 구성 위주로 진행해보고자 한다.
•
일부 미구현된 백엔드 API도 추가적으로 작업할 예정이다.
•
이후 추가적으로 권한에 따른 메뉴 노출 변경 등 프론트엔드 Guard를 사용해보고자 한다.
백엔드 API MVP구성 현재 상태 리뷰
•
백엔드 API는 게시글, 로그인및회원가입, 회원으로 구성되고 있다.
◦
로그인 및 회원가입은 프론트+백엔드 기본은 완료, 카카오 로그인 추가 프론트엔드 구성 필요
◦
게시글은 API 구현은 완료, 프론트엔드는 기본 조회 기능만 구현됨, 프론트엔드 추가 구성 필요
◦
회원은 회원정보수정, 탈퇴 등 API 추가 구성 필요, 프론트엔드도 MyPage 위주로 추가 구현 필요
•
인증 관련 Controller인 auth.controller.ts
◦
카카오 로그인 관련 프론트엔드 추가작업 필요
import { Body, Controller, Get, Logger, Post, Query, Req, Res, UseGuards, UseInterceptors, UploadedFile } from '@nestjs/common';
import { Response } from 'express';
import { AuthService } from './auth.service';
import { SignUpRequestDto } from './dto/sign-up-request.dto';
import { User } from "src/user/user.entity";
import { SignInRequestDto } from './dto/sign-in-request.dto';
import { AuthGuard } from '@nestjs/passport';
import { GetUser } from './get-user.decorator';
import { UserResponseDto } from '../user/dto/user-response.dto';
import { ApiResponse } from 'src/common/api-response.dto';
import { ProfileService } from 'src/file/profile-file.service';
import { FileInterceptor } from '@nestjs/platform-express';
@Controller('api/auth')
export class AuthController {
private readonly logger = new Logger(AuthController.name); // Logger 인스턴스
constructor(private authService: AuthService, private profileService: ProfileService){}
// 회원 가입 기능
@Post('/signup')
@UseInterceptors(FileInterceptor('profilePicture'))
async signUp(@Body() signUpRequestDto: SignUpRequestDto, @UploadedFile() file: Express.Multer.File): Promise<ApiResponse<UserResponseDto>> {
this.logger.verbose(`Attempting to sign up user with email: ${signUpRequestDto.email}`);
const user = await this.authService.signUp(signUpRequestDto);
if (file) {
await this.profileService.uploadProfilePicture(file, user.id);
}
const userResponseDto = new UserResponseDto(user);
this.logger.verbose(`User signed up successfully: ${JSON.stringify(userResponseDto)}`);
return new ApiResponse(true, 201, 'User signed up successfully', userResponseDto);
}
// 로그인 기능
@Post('/signin')
async signIn(@Body() signInRequestDto: SignInRequestDto, @Res() res: Response): Promise<void> {
this.logger.verbose(`Attempting to sign in user with email: ${signInRequestDto.email}`);
const { jwtToken, user } = await this.authService.signIn(signInRequestDto);
const userResponseDto = new UserResponseDto(user);
this.logger.verbose(`User signed in successfully: ${JSON.stringify(userResponseDto)}`);
// [3] 쿠키 설정
res.cookie('Authorization', jwtToken, {
httpOnly: false, // 클라이언트 측 스크립트에서 쿠키 접근 금지
secure: false, // HTTPS에서만 쿠키 전송, 임시 비활성화
maxAge: 3600000, // 1시간
sameSite: 'lax', // CSRF 공격 방어 및 크로스 사이트 요청에서 쿠키 포함
});
res.status(200).json(new ApiResponse(true, 200, 'Sign in successful', { jwtToken, user: userResponseDto }));
}
// 인증된 회원이 들어갈 수 있는 테스트 URL 경로
@Post('/test')
@UseGuards(AuthGuard()) // @UseGuards : 핸들러는 지정한 인증 가드가 적용됨 -> AuthGuard()의 'jwt'는 기본값으로 생략가능
async testForAuth(@GetUser() user: User): Promise<ApiResponse<UserResponseDto>> {
this.logger.verbose(`Authenticated user accessing test route: ${user.email}`);
const userResponseDto = new UserResponseDto(user);
return new ApiResponse(true, 200, 'You are authenticated', userResponseDto);
}
// 카카오 로그인 페이지 요청
@Get('/kakao')
@UseGuards(AuthGuard('kakao'))
async kakaoLogin(@Req() req: Request) {
// 이 부분은 Passport의 AuthGuard에 의해 카카오 로그인 페이지로 리다이렉트
}
// 카카오 로그인 콜백 엔드포인트
@Get('kakao/callback')
async kakaoCallback(@Query('code') kakaoAuthResCode: string, @Res() res: Response) { // Authorization Code 받기
const { jwtToken, user } = await this.authService.signInWithKakao(kakaoAuthResCode);
// 쿠키에 JWT 설정
res.cookie('Authorization', jwtToken, {
httpOnly: true, // 클라이언트 측 스크립트에서 쿠키 접근 금지
secure: false, // HTTPS에서만 쿠키 전송, 임시 비활성화
maxAge: 3600000, // 1시간
sameSite: 'lax', // CSRF 공격 방어
});
const userResponseDto = new UserResponseDto(user);
this.logger.verbose(`User signed in successfully: ${JSON.stringify(userResponseDto)}`);
res.status(200).json(new ApiResponse(true, 200, 'Sign in successful', { jwtToken, user: userResponseDto }));
}
}
TypeScript
복사
•
회원 정보 관련된 user.controller.ts
◦
mypage를 접근하기 위한 특정 회원 정보 조회 정도 기능만 구현되어있다.
◦
회원 정보 수정, 삭제 등 추가 API 구성이 필요하다.
import { Controller, Get, Logger, Param, UseGuards } from '@nestjs/common';
import { UserService } from './user.service';
import { AuthGuard } from '@nestjs/passport';
import { RolesGuard } from 'src/auth/custom-role.guard';
import { UserResponseDto } from 'src/user/dto/user-response.dto';
import { ApiResponse } from 'src/common/api-response.dto';
import { UserWithFilesResponseDto } from './dto/user-with-files-response.dto';
@Controller('api/users')
@UseGuards(AuthGuard('jwt'), RolesGuard)
export class UserController {
private readonly logger = new Logger(UserController.name);
constructor(private userService: UserService){}
// 특정 번호의 회원 정보 조회
@Get(':id')
async getUserById(@Param('id') id: number): Promise<ApiResponse<UserWithFilesResponseDto>> {
this.logger.verbose(`Retrieving User with ID ${id}`);
const user = await this.userService.findOneByIdWithFiles(id);
const userDto = new UserWithFilesResponseDto(user);
this.logger.verbose(`User retrieved successfully: ${JSON.stringify(userDto)}`);
return new ApiResponse(true, 200, 'User retrieved successfully', userDto);
}
}
TypeScript
복사
[NestJS] 17. 게시글 및 회원 관리 기능 보완(CRUD) 리팩토링

1. 프론트엔드 Slide 기능 구현
1. Swiper.js 설치
슬라이드 효과 구현을 위한 Swiper 설치
•
IONIC 에서는 7버전 이후로 ion-slides 라는 태그의 기능이 제거되었다.

•
공식 문서에서도 슬라이드 기능을 위해서는 Swiper.js 를 활용하여 추가 할 것을 안내해주고 있다.
•
아래 명령어를 통해 프로젝트에 swiper를 설치한다.
npm i swiper
Shell
복사
[NestJS] 19. Slide(Swiper) 기능 구현

1. 시스템 아키텍쳐(System Architecture)
1.1 System Architecture란?
시스템 아키텍쳐(System Architecture)란?
•
시스템 또는 서비스의 구조를 정의하고 구성 요소 간의 관계를 정의, 설계하는 것을 말함
•
시스템 구성 및 동작 원리를 나타냄
•
시스템 구성요소에 대해 설계 및 구현을 지원하는 수준으로 자세히 기술
•
구성요소 간의 관계 및 시스템 외부 환경과 관계가 나타남
•
요구사항 및 시스템 전체 수명 주기를 고려
•
시스템 전체에 대한 논리적인 기능 체계와 실현을 위한 구성방식, 최적화를 목표
•
소프트웨어, 하드웨어, 네트워크, 데이터베이스 등 다양한 요소를 포함
시스템 아키텍쳐의 핵심 요소
•
구성 요소 (Components)
◦
시스템을 구성하는 주요 요소들 (예: 서버, 데이터베이스, 클라이언트 애플리케이션 등)
•
상호작용 (Interactions)
◦
구성 요소 간의 데이터 흐름 및 통신 방식
•
데이터 흐름 (Data Flow)
◦
데이터가 시스템 내에서 어떻게 흐르며 처리되는지에 대한 내용
•
배포 (Deployment)
◦
구성 요소가 물리적으로 어떻게 배포되는지에 대한 내용
•
보안 (Security)
◦
시스템을 보호하기 위한 방법과 정책 (예: 인증, 권한 부여)
•
비용 (Cost)
◦
시스템 운영 및 유지에 드는 비용
•
확장성 (Scalability)
◦
시스템이 증가하는 트래픽이나 데이터 양을 처리할 수 있는 능력
•
유지보수성 (Maintainability)
◦
시스템을 쉽게 업데이트하고 관리할 수 있는 능력
•
성능 (Performance)
◦
시스템의 반응 속도 및 처리 능력
[NestJS] 20. Architecture Diagram 그리기와 README.md 프로젝트 소개 꾸미기

1. AWS란?
Amazon Web Service
•
아마존에서 제공하는 클라우드 컴퓨팅 서비스 플랫폼
◦
AWS는 다양한 IT 리소스를 인터넷을 통해 제공하며, 사용자는 필요에 따라 서버, 스토리지, 데이터베이스, 네트워킹, 머신 러닝, 분석 도구 등 다양한 서비스를 이용할 수 있음
◦
AWS는 스타트업부터 대기업까지 다양한 규모의 기업에서 사용되며, 클라우드 기반의 애플리케이션 개발, 데이터 저장, 웹 호스팅 등 여러 용도로 활용
AWS의 강점
•
확장성
◦
스케일링 : 사용자는 필요에 따라 리소스를 쉽게 확장하거나 축소
▪
Scale Up/Down : 수직 확장, 간단히 컴퓨터 스펙자체를 올리는 행위라 이해
▪
Scale Out/In : 수평 확장, 간단히 컴퓨터를 병렬로 여러대를 배치하는 행위로 이해
•
비용 효율성
◦
사용한 만큼만 비용을 지불하는 Pay-as-you-go 모델을 채택하고 있어 초기 투자 비용이 낮음
•
다양한 서비스
◦
컴퓨팅, 스토리지, 데이터베이스, 네트워킹, 보안 등 웹 서비스를 위해 필요한 부분을 전반적으로 관리
•
통합 서비스
◦
서버와 각종 서비스간의 연결 설정을 간편화하여 기존 개발자가 직접 설정해야하는 여러 연결 설정 부분을 최소화 시켜 개발에 집중 할 수 있는 환경을 제공
•
글로벌 인프라
◦
전 세계 여러 지역에 데이터 센터를 운영하여 높은 가용성과 낮은 지연 시간을 제공
•
보안
◦
강력하고 검증된 보안 기능과 인증 절차를 통해 데이터 보호를 강화
[NestJS] 21. AWS 클라우드 서비스와 배포

클라이언트 약식 소셜 로그인(OAuth)을 백엔드 서버 중심 인증방식으로 변경
•
보안 및 관례상 프로덕션 환경에서는 Credential 획득 및 관리를 백엔드에서 처리하는 것이 일반적인 관례이자 더 안전한 방식
•
따라서 이전 클라이언트 사이드로 Credential을 전달하던 약식 구현에서 백엔드 중앙집중식 구현으로 변경하고자 한다.
[OAuth2.0] Google Social Login 구현 - 백엔드 중앙 관리식 로직 구현

소셜 로그인은 모두 비슷한 과정
•
이전 카카오 로그인 구현과 결과적으로 동일한 과정을 거친다.
1.
인증 제공자(Provider)인 Google 디벨로퍼 콘솔에 접근해서 API 기능을 이용할 애플리케이션을 생성
2.
REST API 키 등 인증 키, 토큰 등을 받음
3.
해당 토큰과 함께 백엔드서버가 프로바이더 서버로 요청하는 중첩 구조를 만들게 됨
•
일반로그인 : 사용자 → 서버(서비스 서버) → 사용자
•
소셜로그인 : 사용자 → 서버(서비스 서버 → 프로바이더 서버 → 서비스 서버) → 사용자
•
실제 애플리케이션에서 구현하는 과정을 정리
[OAuth2.0] Google Social Login 구현 - 클라이언트 사이드 약식

데이터 바인딩을 위한 @Input 데코레이터
@input 데코레이터를 통한 데이터 바인딩
Angular에서 데이터 바인딩을 위한 데코레이터는 주로 @Input()을 사용하여 부모 컴포넌트로부터 값을 받아오는 데 사용됩니다. 이 데코레이터는 자식 컴포넌트에서 부모 컴포넌트의 데이터를 바인딩할 수 있게 해줍니다.
다음은 Angular에서 @Input() 데코레이터를 사용하여 데이터 바인딩을 구현하는 간단한 예시입니다.
1.
자식 컴포넌트 생성
먼저 자식 컴포넌트를 생성하고, @Input()을 사용하여 데이터를 받을 준비를 합니다.
Angular의 @Input 데코레이터 사용 방법

Standalone?
Standardalone?
Angular에서 "Standalone" 컴포넌트는 Angular 14 이상에서 도입된 기능으로, 모듈 없이 독립적으로 사용할 수 있는 컴포넌트를 의미합니다. 이는 컴포넌트의 재사용성과 관리성을 높이며, 더 간편하게 애플리케이션을 구성할 수 있게 해줍니다.
Standalone 컴포넌트의 특징
1.
모듈 불필요: 기존의 Angular 컴포넌트는 반드시 모듈에 포함되어야 했지만, Standalone 컴포넌트는 독립적으로 사용할 수 있습니다.
컴포넌트의 standalone의 필요성과 적용 범위 고려










