이 포스트는 NestJS, PostgreSQL, Docker를 활용한 회원가입 시스템 구축 과정을 실습 중심으로 설명합니다. 실제 강의에서 제공된 코드를 기반으로, 각각의 파일이 어떤 역할을 하는지 기술 블로그 스타일로 정리했습니다.
1. 환경 구성 및 의존성
.dockerignore
NestJS 환경에서 Docker 이미지 생성 시 불필요한 파일을 제외하기 위한 설정입니다.
node_modules dist .git .gitignore Dockerfile docker-compose.yml *.log *.env
설치 라이브러리
pnpm add @nestjs/config @nestjs/jwt @nestjs/typeorm @nestjs bcrypt class-transformer class-validator joi pg typeorm
이 라이브러리들은 NestJS 앱 구성, 유효성 검사, DB 연동, 암호화 등에 사용됩니다.
2. Docker Compose 구성
docker-compose.yml
NestJS 앱과 PostgreSQL을 함께 실행하기 위한 설정입니다.
services: user: build: context: . dockerfile: ./apps/user/Dockerfile target: development command: pnpm run start:dev user depends_on: postgres_user: condition: service_healthy env_file: - ./apps/user/.env ports: - '3001:3000' volumes: - .:/usr/src/app - /usr/src/app/node_modules postgres_user: image: postgres:16 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres ports: - '6001:5432' volumes: - ./postgres/user:/var/lib/postgresql/data healthcheck: test: ['CMD-SHELL', 'pg_isready -U postgres'] interval: 5s timeout: 5s retries: 10 start_period: 5s
3. 앱 모듈 구성 (AppModule)
app.module.ts
@Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, validationSchema:Joi.object({ HTTP_PORT: Joi.number().required(), DB_URL: Joi.string().required(), }), }), TypeOrmModule.forRootAsync({ useFactory: (configService: ConfigService) => ({ type: 'postgres', url: configService.getOrThrow('DB_URL'), autoLoadEntities: true, synchronize: true }), inject: [ConfigService], }), UserModule, AuthModule, ], }) export class AppModule {}
4. 유저 Entity 설계
user.entity.ts
@Entity() export class User { @PrimaryGeneratedColumn('uuid') id: string; @Column({ unique: true }) email: string; @Column() name: string; @Column() age: number; @Column() profile: string; @Column({ select: false }) password: string; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; @VersionColumn() version: number; }
5. DTO 유효성 검증
create-user.dto.ts
export class CreateUserDto{ @IsEmail() @IsNotEmpty() email: string; @IsString() @IsNotEmpty() password: string; @IsString() @IsNotEmpty() name: string; @IsNumber() @IsNotEmpty() age: number; @IsString() @IsNotEmpty() profile: string; }
6. 사용자 서비스 (UserService)
user.service.ts
@Injectable() export class UserService { constructor( @InjectRepository(User) private readonly userRepository: Repository<User>, ){} async getUserById(userId: string){ const user = await this.userRepository.findOneBy({id: userId}); if(!user){ throw new BadRequestException('존재하지 않는 사용자입니다!'); } return user; } async create(createUserDto: CreateUserDto){ const {email, password} = createUserDto; const user = await this.userRepository.findOne({ where:{ email } }); if(user){ throw new BadRequestException('이미 가입한 이메일 입니다!') } const hash = await bcrypt.hash(password, 10); await this.userRepository.save({ ...createUserDto, email, password: hash, }); return this.userRepository.findOne({ where:{ email } }); } }
7. 인증 서비스 (AuthService)
auth.service.ts
@Injectable() export class AuthService { constructor(private readonly userService: UserService) {} async register(rawToken: string, registerDto: any) { const { email, password } = this.parseBasicToken(rawToken); return this.userService.create({ ...registerDto, email, password, }); } parseBasicToken(rawToken: string) { const basicSplit = rawToken.split(' '); if (basicSplit.length !== 2) { throw new BadRequestException('토큰 포맷이 잘못됐습니다!'); } const [basic, token] = basicSplit; if (basic.toLowerCase() !== 'basic') { throw new BadRequestException('토큰 포맷이 잘못됐습니다!'); } const decoded = Buffer.from(token, 'base64').toString('utf-8'); const tokenSplit = decoded.split(':'); if (tokenSplit.length !== 2) { throw new BadRequestException('토큰 포맷이 잘못됐습니다!'); } const [email, password] = tokenSplit; return { email, password }; } }
8. 사용자 정의 데코레이터
authorization.decorator.ts
export const Authorization = createParamDecorator( (data: any, context: ExecutionContext) => { const req = context.switchToHttp().getRequest(); return req.headers['authorization']; } );
9. Webpack 설정
webpack.config.js
module.exports = function(options){ return { ...options, devtool: 'source-map', } }
댓글 ( 0)
댓글 남기기