인프런 커뮤니티 질문&답변

CB S님의 프로필 이미지
CB S

작성한 질문수

Slack 클론 코딩[백엔드 with NestJS + TypeORM]

Exception Filter

Exception Filter편에서 POST할 때 EntityMetadataNotFoundError에러가 납니다.

작성

·

350

·

수정됨

0

// users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from '../entities/Users';

@Module({
  imports: [TypeOrmModule.forFeature([Users])],
  providers: [UsersService],
  controllers: [UsersController],
})
export class UsersModule {}
// app.module.ts
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { LoggerMiddleware } from './middlewares/logger.middleware';
import { UsersModule } from './users/users.module';
import { WorkspacesModule } from './workspaces/workspaces.module';
import { ChannelsModule } from './channels/channels.module';
import { DmsModule } from './dms/dms.module';
import { UsersService } from './users/users.service';
import * as process from 'process';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from './entities/Users';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    UsersModule,
    WorkspacesModule,
    ChannelsModule,
    DmsModule,
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: process.env.DB_USER,
      password: process.env.DB_PW,
      database: process.env.DB_NAME,
      entities: ['./entities/*.{js,ts}'],
      synchronize: false,
      logging: true,
      keepConnectionAlive: true,
      migrations: [__dirname + '/src/migrations/*.ts'],
    }),
    TypeOrmModule.forFeature([Users]),
  ],
  controllers: [AppController],
  providers: [AppService, UsersService],
})
export class AppModule {
  configure(consumer: MiddlewareConsumer): void {
    consumer.apply(LoggerMiddleware).forRoutes('*');
  }
}
// users.controller.ts
import {
  Body,
  Controller,
  Post,
  Get,
  Req,
  Res,
  UseInterceptors,
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { JoinRequestDto } from './dto/join.request.dto';
import { UsersService } from './users.service';
import { UserDto } from '../common/dto/user.dto';
import { User } from '../common/decorators/user.decorator';
import { UndefinedToNullInterceptor } from '../common/interceptors/undefinedToNull.interceptor';

@UseInterceptors(UndefinedToNullInterceptor)
@ApiTags('USER')
@Controller('api/users')
export class UsersController {
  constructor(private usersService: UsersService) {}

//.....

  @ApiOperation({ summary: '회원가입' })
  @Post()
  async join(@Body() body: JoinRequestDto) {
    await this.usersService.join(body.email, body.nickname, body.password);
  }
import { HttpException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Users } from '../entities/Users';
import bcrypt from 'bcrypt';
@Injectable()
export class UsersService {
  constructor() {}
  @InjectRepository(Users)
  private usersRepository: Repository<Users>;
  getUser() {}
  async join(email: string, nickname: string, password: string) {
    const user = this.usersRepository.findOne({ where: { email } });
    if (!email) {
      throw new HttpException('이메일이 없습니다.', 400);
    }
    if (!nickname) {
      throw new HttpException('닉네임이 없습니다.', 400);
    }
    if (!password) {
      throw new HttpException('비밀번호가 없습니다.', 400);
    }
    if (user) {
      throw new HttpException('이미 존재하는 사용자입니다.', 400);
    }
    const hashedPassword = await bcrypt.hash(password, 12);
    await this.usersRepository.save({
      email,
      nickname,
      password: hashedPassword,
    });
  }
}
// a-nest/src/entities/Users.ts
import {
  Column,
  Entity,
  Index,
  OneToMany,
  PrimaryGeneratedColumn,
} from "typeorm";
import { Channelchats } from "./Channelchats";
import { Channelmembers } from "./Channelmembers";
import { Dms } from "./Dms";
import { Mentions } from "./Mentions";
import { Workspacemembers } from "./Workspacemembers";
import { Workspaces } from "./Workspaces";

@Index("email", ["email"], { unique: true })
@Entity("users", { schema: "sleact" })
export class Users {
  @PrimaryGeneratedColumn({ type: "int", name: "id" })
  id: number;

  @Column("varchar", { name: "email", unique: true, length: 30 })
  email: string;

  @Column("varchar", { name: "nickname", length: 30 })
  nickname: string;

  @Column("varchar", { name: "password", length: 100 })
  password: string;

  @Column("datetime", { name: "createdAt" })
  createdAt: Date;

  @Column("datetime", { name: "updatedAt" })
  updatedAt: Date;

  @Column("datetime", { name: "deletedAt", nullable: true })
  deletedAt: Date | null;

  @OneToMany(() => Channelchats, (channelchats) => channelchats.user)
  channelchats: Channelchats[];

  @OneToMany(() => Channelmembers, (channelmembers) => channelmembers.user)
  channelmembers: Channelmembers[];

  @OneToMany(() => Dms, (dms) => dms.sender)
  dms: Dms[];

  @OneToMany(() => Dms, (dms) => dms.receiver)
  dms2: Dms[];

  @OneToMany(() => Mentions, (mentions) => mentions.sender)
  mentions: Mentions[];

  @OneToMany(() => Mentions, (mentions) => mentions.receiver)
  mentions2: Mentions[];

  @OneToMany(
    () => Workspacemembers,
    (workspacemembers) => workspacemembers.user
  )
  workspacemembers: Workspacemembers[];

  @OneToMany(() => Workspaces, (workspaces) => workspaces.owner)
  workspaces: Workspaces[];
}

 

서버 실행 후 http://localhost:3002/api/users URL로 빈 값을 POST로 보내면 콘솔창에 400 이메일이 없습니다. 라는 문구가 뜬 이후에

C:\Users\xxx\Documents\Project\NestProject\a-nest\src\data-source\DataSource.ts:448
        if (!metadata) throw new EntityMetadataNotFoundError(target)
                             ^
EntityMetadataNotFoundError: No metadata for "Users" was found.
    at DataSource.getMetadata (C:\Users\xxx\Documents\Project\NestProject\a-nest\src\data-source\DataSource.ts:448:30)
    at Repository.get metadata [as metadata] (C:\Users\xxx\Documents\Project\NestProject\a-nest\src\repository\Repository.ts:53:40)
    at Repository.findOne (C:\Users\xxx\Documents\Project\NestProject\a-nest\src\repository\Repository.ts:597:42)
    at UsersService.join (C:\Users\xxx\Documents\Project\NestProject\a-nest\src\users\users.service.ts:13:39)
    at UsersController.join (C:\Users\xxx\Documents\Project\NestProject\a-nest\src\users\users.controller.ts:37:29)
    at C:\Users\xxx\Documents\Project\NestProject\a-nest\node_modules\@nestjs\core\router\router-execution-context.js:38:29
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

위와 같이 EntityMetadataNotFoundError에러가 발생합니다. DB 커넥션 부분도 확인해보고, entities 부분이 문제인가 싶어 아래와 같이 변경도 해보았지만

TypeOrmModule.forRoot({
      entities: ['./entities/*.{js,ts}'],
  ],

해결되지 않아 질문드립니다.

추가적으로 \data-source\DataSource.ts 부분과

\repository\Repository.ts부분은 실제 디렉토리에 없는 것들인데 왜 뜨는지도 모르겠습니다.

해결 방법이 있을까요?

답변 2

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

에러가 throw되면 interceptor나 exceptionFilter에 걸려야하는데요. UndefinedToNull인터셉터와 글로벌 필터 코드 보여주세요

CB S님의 프로필 이미지
CB S
질문자

// undefinedToNull.interceptor.ts
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common';
import { map, Observable } from 'rxjs';

@Injectable()
export class UndefinedToNullInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext,
    next: CallHandler<any>,
  ): Observable<any> | Promise<Observable<any>> {
    return next
      .handle()
      .pipe(map((data) => (data === undefined ? null : data)));
  }
}

혹시 글로벌 필터 코드가 httpException.filter.ts을 의미 하시는걸까요?

// httpException.filter.ts
import { Catch, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter {
  catch(exception, host) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
    const status = exception.getStatus();
    const err = exception.getResponse() as
      | string
      | { error: string; statusCode: 400; message: string[] };

    console.log(status, err);
    // response.status(status).json({
    //   statusCode: status,
    //   timestamp: new Date().toISOString(),
    //   path: request.url,
    // });
  }
}

 

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

네 지금 글로벌 필터에서 아무것도 하지 않고 있네요.

0

안녕하세요, 인프런 AI 인턴입니다. CB S님,

제공해주신 정보를 바탕으로 살펴보았을 때, EntityMetadataNotFoundError는 TypeORM이 데이터베이스 테이블과 연결될 엔티티의 메타데이터를 찾을 수 없을 때 발생하는 오류입니다. 이 문제는 대개 엔티티 파일의 위치가 잘못되었거나, TypeOrmModule에 엔티티를 잘못 등록했을 때 발생합니다.

질문에 있으신 코드와 에러 메시지에서 확인할 수 있는 몇 가지 지점은 아래와 같습니다:

  1. entities의 경로가 문자열로 처리되고, .ts.js 둘 다 포함되어 있으나, 실제 애플리케이션을 실행하는 환경(개발환경 또는 프로덕션)에서 올바르게 해석되지 않을 가능성이 있습니다. 실제 개발 환경에서는 .ts 파일로 작업하며, 빌드 후 프로덕션 환경에서는 .js로 변환되기 때문입니다.

  2. \data-source\DataSource.ts\repository\Repository.ts 부분이 실제로 없다고 하셨는데, 이는 TypeORM이 내부적으로 사용하는 파일들입니다. 디렉토리에 직접적으로 존재하지 않아도 TypeORM이 작업을 수행하면서 런타임에 참조하는 것일 수 있습니다.

해결 방법으로는 다음과 같은 접근을 시도해보실 수 있습니다:

  • entities 경로를 절대경로로 제공하거나, 애플리케이션이 실행되는 환경에 맞게 확장자를 명확하게 지정해보세요.
  • 만약 빌드 후 .js 파일들이 위치한 dist 디렉토리를 참조해야 한다면, 해당 디렉토리 경로를 정확하게 지정해주셔야 합니다.

유사한 문제를 해결한 다른 질문들을 참고하셔도 좋을 것 같습니다:

  1. DB 생성시 Error: Cannot find module ‘./index.js가 계속 뜨는데 뭐가 문제인가요??

    • 이 질문에서는 경로 문제로 인해 모듈을 찾지 못하는 상황이 발생했으며, 제로초 강사님이 해당 문제를 해결하기 위해 구체적인 경로 수정 방법을 제시하셨습니다.
  2. typeorm을 통해 db 생성을 하려고 하는데 에러가 발생합니다.

    • 이 경우에는 TypeORM 설정 시 entities에 대한 올바른 등록이 필요했었습니다.
  3. .env error

    • 환경변수 에러 관련 질문으로, 모듈 참조 방식이나 환경변수 설정에 관한 해결책이 제시되었습니다.

기술적인 문제 해결의 경우, 상황에 따라 다양한 요소를 검토해야 하므로 질문 내용에서 제시된 내용만으로는 정확한 원인을 파악하기 어려울 수 있습니다. 따라서 위의 내용을 참고해보시되, 문제가 해결되지 않는다면 추가적으로 강의 내용을 검토하시거나 직접 강사님께 상세한 상황을 설명하신 후 보다 정확한 답변을 받으실 수 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

CB S님의 프로필 이미지
CB S
질문자

app.module.ts부분 중 아래와 같이 entities를 가져오는 부분을 수정했습니다.

@Module({
 ...
      entities: [
        Users,
        Channels,
        Dms,
        Channelchats,
        Mentions,
        Channelmembers,
        Workspacemembers,
        Workspaces,
      ],
    ...
}

서버는 끊기지 않았지만

400 이메일이 없습니다.
query: SELECT `Users`.`id` AS `Users_id`, `Users`.`email` AS `Users_email`, `Users`.`nickname` AS `Users_nickname`, `Users`.`password` AS `Users_password`, `Users`.`createdAt` AS `Users_createdAt`, `Users`.`updatedAt` AS `Users_updatedAt`, `Users`.`deletedAt` AS `Users_deletedAt` FROM `users` `Users` LIMIT 
1

콘솔창에 위와 같이 떴고, POSTMAN에서는 Sending request...가 뜨면서 무한 로딩에 걸렸습니다.

CB S님의 프로필 이미지
CB S

작성한 질문수

질문하기