icon

백엔드 API 구현


 NestJS를 사용한 백엔드 API 구현은 모듈화된 구조, 의존성 주입, 그리고 강력한 데코레이터 시스템을 활용하여 효율적이고 확장 가능한 애플리케이션을 만드는 과정입니다.

프로젝트 구조 설정

  1. NestJS CLI를 사용한 프로젝트 생성
nest new my-project
cd my-project
  1. 기본 프로젝트 구조
src/
├── app.module.ts
├── main.ts
├── users/
│   ├── users.module.ts
│   ├── users.controller.ts
│   ├── users.service.ts
│   └── dto/
├── auth/
│   ├── auth.module.ts
│   ├── auth.service.ts
│   └── jwt.strategy.ts
└── common/
    ├── filters/
    ├── interceptors/
    └── pipes/

모듈, 컨트롤러, 서비스 구현

  1. 모듈 구현
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}
  1. 컨트롤러 구현
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
 
  @Get()
  findAll(): Promise<User[]> {
    return this.usersService.findAll();
  }
 
  @Post()
  create(@Body() createUserDto: CreateUserDto): Promise<User> {
    return this.usersService.create(createUserDto);
  }
}
  1. 서비스 구현
@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}
 
  async findAll(): Promise<User[]> {
    return this.usersRepository.find();
  }
 
  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.usersRepository.create(createUserDto);
    return this.usersRepository.save(user);
  }
}

데이터베이스 연동

 TypeORM을 사용한 데이터베이스 연동

  1. 의존성 설치
npm install @nestjs/typeorm typeorm pg
  1. TypeORM 설정 (app.module.ts)
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'root',
      password: 'password',
      database: 'test',
      entities: [User],
      synchronize: true,
    }),
  ],
})
export class AppModule {}
  1. 엔티티 정의
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;
 
  @Column()
  name: string;
 
  @Column({ unique: true })
  email: string;
}

인증 및 권한 부여

 JWT를 사용한 인증 구현

  1. 의존성 설치
npm install @nestjs/jwt passport-jwt
  1. JWT 전략 구현
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: 'your-secret-key',
    });
  }
 
  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}
  1. 가드 사용
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Request() req) {
  return req.user;
}

미들웨어, 인터셉터, 파이프, 가드 활용

  1. 로깅 미들웨어
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    console.log('Request...');
    next();
  }
}
  1. 변환 인터셉터
@Injectable()
export class TransformInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(map(data => ({ data })));
  }
}
  1. 유효성 검사 파이프
@Post()
create(@Body(new ValidationPipe()) createUserDto: CreateUserDto) {
  return this.usersService.create(createUserDto);
}
  1. 역할 기반 가드
@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const roles = Reflect.getMetadata('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}

에러 처리 및 로깅

 전역 예외 필터 구현

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  constructor(private readonly logger: Logger) {}
 
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR;
 
    const message = exception instanceof HttpException
      ? exception.getResponse()
      : 'Internal server error';
 
    this.logger.error(
      `${request.method} ${request.url}`,
      exception instanceof Error ? exception.stack : null,
      'ExceptionFilter',
    );
 
    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message,
    });
  }
}

API 버저닝, 문서화, 테스팅

  1. API 버저닝
@Controller('api/v1/users')
export class UsersControllerV1 {}
 
@Controller('api/v2/users')
export class UsersControllerV2 {}
  1. Swagger를 사용한 API 문서화
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
 
const config = new DocumentBuilder()
  .setTitle('Users API')
  .setDescription('The users API description')
  .setVersion('1.0')
  .addTag('users')
  .build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
  1. 테스팅
describe('UsersController', () => {
  let controller: UsersController;
  let service: UsersService;
 
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [UsersController],
      providers: [UsersService],
    }).compile();
 
    controller = module.get<UsersController>(UsersController);
    service = module.get<UsersService>(UsersService);
  });
 
  it('should be defined', () => {
    expect(controller).toBeDefined();
  });
 
  it('should return an array of users', async () => {
    const result = [{ id: 1, name: 'Test User' }];
    jest.spyOn(service, 'findAll').mockImplementation(() => result);
 
    expect(await controller.findAll()).toBe(result);
  });
});

성능 최적화

  1. 캐싱 구현
import { CacheInterceptor } from '@nestjs/cache-manager';
 
@UseInterceptors(CacheInterceptor)
@Get()
findAll(): Promise<User[]> {
  return this.usersService.findAll();
}
  1. 데이터베이스 인덱싱
@Entity()
export class User {
  @Index()
  @Column()
  email: string;
}

Best Practices 및 주의사항

  1. 단일 책임 원칙 준수 : 각 모듈, 서비스, 컨트롤러의 역할 명확히 분리
  2. 의존성 주입 활용 : 결합도 낮추고 테스트 용이성 증가
  3. DTO 사용 : 데이터 유효성 검증 및 타입 안정성 확보
  4. 비즈니스 로직은 서비스 계층에 구현
  5. 환경 변수 사용 : 설정 정보 외부화
  6. 에러 처리 일관성 유지
  7. 로깅 전략 수립 : 운영 환경에서의 문제 해결 용이성 확보
  8. 성능 모니터링 및 최적화 : 주기적인 성능 테스트 및 개선
  9. 보안 Best Practices 적용 : HTTPS, CORS 설정, 입력 유효성 검사 등
  10. 코드 품질 관리 : 린트 도구 사용, 코드 리뷰 프로세스 수립