이 포스트는 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)
댓글 남기기