44,000원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
test 관련 질문드립니다!
안녕하세요 제로초님! test관련 질문이 있어서 질문글 남깁니다 :) 아래와 같이 따로 Repository provider를 만들고 export const userProviders = [ { provide: USER_REPOSITORY, useFactory: (dataSource: DataSource) => dataSource.getRepository(User), inject: [APP_DATABASE], }, ]; Service에서 아래와 같이 @InjectRepository가 아니라 @Inject로 생성자를 구성했습니다. (USER_REPOSITORY는 따로 constants.ts파일을 만들어두었습니다. @Injectable() export class UserService { constructor( @Inject(USER_REPOSITORY) private userRepository: Repository<User>, ) {} . . . 이후 Service 파일의 테스트 코드를 작성하는데 모킹이 안돼서 질문을 남기게 되었습니다. . . . providers: [ UserService, { provide: getRepositoryToken(User), useValue: mockUserRepository, }, ], . . . 위와 같이 모킹했는데, 아래와 같은 오류가 떴습니다. ㅜㅜ Nest can't resolve dependencies of the UserService (?). Please make sure that the argument USER_REPOSITORY at index [0] is available in the RootTestModule context. Potential solutions: - If USER_REPOSITORY is a provider, is it part of the current RootTestModule? - If USER_REPOSITORY is exported from a separate @Module, is that module imported within RootTestModule? @Module({ imports: [ /* the Module containing USER_REPOSITORY */ ] }) at TestingInjector.lookupComponentInParentModules (../node_modules/@nestjs/core/injector/injector.js:231:19) at TestingInjector.resolveComponentInstance (../node_modules/@nestjs/core/injector/injector.js:184:33) at TestingInjector.resolveComponentInstance (../node_modules/@nestjs/testing/testing-injector.js:16:45) at resolveParam (../node_modules/@nestjs/core/injector/injector.js:106:38) at async Promise.all (index 0) at TestingInjector.resolveConstructorParams (../node_modules/@nestjs/core/injector/injector.js:121:27) at TestingInjector.loadInstance (../node_modules/@nestjs/core/injector/injector.js:52:9) at TestingInjector.loadProvider (../node_modules/@nestjs/core/injector/injector.js:74:9) at async Promise.all (index 3)
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
apiProperty()
swagger에서 apiProperty()에서 type으로 dto 자기 자신을 나타내진 못하나요? 동일한 dto 구조로 내부에 작성하고 싶은데 안된다면 어떻게 진행해야할까요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
공지내용대로 해도 여전히 마이그레이션시 dataSource를 찾을 수 없다는 에러가 뜹니다.
제로초님 안녕하세요. typeorm 0.3 버전 사용에 대한 빠른 대응 감사합니다. 저 그런데 공지사항에 올려주신대로 해도 에러메시지가 뜨면서 실행이 안 됩니다. 공지에 올려주신 것처럼 src 폴더 아래 dataSource.ts 파일을 만들었고, https://github.com/typeorm/typeorm/issues/8810#issuecomment-1084255476 여기에 나온 것을 참고해서 npx typeorm migration:generate src/migrations/categoryToType -d src/dataSource.ts 와 npx typeorm migration:run -d src/dataSource.ts를 실행시켜면 Unable to open file: '.../src/dataSource.ts' 라는 에러가 뜨면서 안 됩니다. package.json의 script에 "db:migrate": "npm run typeorm migration:run -d ./src/dataSource.ts"로 지정해서 실행시켜보면 Missing required argument: dataSource라고 나오고요. 어떻게 해결해야할까요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
공지내용(typeorm@0.3 관련)관련해서 도움 요청 드립니다.
제로초님 시간이 가능하시면 메일로 주신 공지내용(typeorm@0.3 관련)관련해서 좀 더 참고할 수 있는(강의 내용 일부 기능) 소스 또는 추가 강의 부탁드립니다. dataSource.ts 파일의 const dataSource = new DataSource({...})로 dataSource 인스턴스를 생성했는데, app.module 에서 ormconfig 대신에 TypeOrmModule.forRoot({...}) 로 DB 연결을 또 생성을 하게 되면 연결이 다른 컨넥션 2개를 가지게 되는 건가요? 공지내용 첫 소스 내용에 "private datasource: DataSource" 부분에 이렇게 DI를 하게 되면 const dataSource = new DataSource({...}) 부분의 dataSource 의 DB 연결을 가지고 오게 되나요? 0.2.x 보다는 0.3.x 가 query 작성하는 부분에서 많이 편한게 보여서 0.3.x로 프로토타입을 만들어 보고 싶습니다.
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
쿼리빌더 사용과 조인과 셀렉트에 관련된 질문입니다.
안녕하세요 조현영님. 이번에는 쿼리빌더로 원하는 데이터를 얻기 위해 조인과 셀렉트 과정에서 오류가 발생하여 질문드립니다. 일단 질문을 드리기 전에 제 상황에 대한 사전지식이 필요하기 때문에 코드를 같이 보여드리겠습니다. @Entity("users") export class UserEntity extends CommonEntity { @OneToOne(() => UserProfileEntity, (common) => common.id) @JoinColumn({ name: "commonId", referencedColumnName: "id" }) profile: UserProfileEntity; @OneToOne(() => UserAuthEntity, (auth) => auth.id) @JoinColumn({ name: "authId", referencedColumnName: "id" }) auth: UserAuthEntity; @OneToOne(() => UserActivityEntity, (activity) => activity.id) @JoinColumn({ name: "activityId", referencedColumnName: "id" }) activity: UserActivityEntity; } 사용자에 관련된 엔티티입니다. 사용자와 관련된 컬럼들은 세부적으로 엔티티를 나눈 다음 그 엔티티안에 정의 해두었습니다. 사용자 엔티티에는 프로필, 인증, 활동 엔티티의 아이디만 존재합니다. 아래에는 사용자 엔티티에게 상속을 해주는 CommonEntity입니다. export abstract class CommonEntity { @IsUUID() @PrimaryGeneratedColumn("uuid") id: string; @CreateDateColumn({ type: "timestamp" }) createdAt: Date; @UpdateDateColumn({ type: "timestamp" }) updatedAt: Date; @DeleteDateColumn({ type: "timestamp" }) deletedAt: Date | null; } 아래에는 각각 프로필, 인증, 활동 엔티티의 대한 코드입니다. @Entity("users profile") export class UserProfileEntity { @PrimaryGeneratedColumn("uuid") id: string; @IsString() @IsNotEmpty() @Column({ type: "varchar", length: 20, nullable: false }) realname: string; @IsDateString() @IsNotEmpty() @Column({ type: "date", nullable: false }) birth: Date; @IsEnum(["male", "female"]) @IsNotEmpty() @Column({ type: "enum", enum: ["male", "female"] }) gender: "male" | "female"; @IsMobilePhone() @IsNotEmpty() @Column({ type: "varchar", length: 15, unique: true, nullable: false }) phonenumber: string; @OneToOne(() => UserEntity) user: UserEntity; } @Entity("users auth") export class UserAuthEntity { @PrimaryGeneratedColumn("uuid") id: string; @IsString() @IsNotEmpty() @Column({ type: "varchar", length: 20, unique: true, nullable: false }) nickname: string; @IsEmail() @IsNotEmpty() @Column({ type: "varchar", length: 60, unique: true, nullable: false }) email: string; @IsString() @IsNotEmpty() @Matches(/^[A-Za-z\d!@#$%^&*()]{8,30}$/) @Column({ type: "varchar", nullable: false }) password: string; @Column({ type: "enum", enum: ["general", "special", "admin"], default: "general", }) userType: "general" | "special" | "admin"; @OneToOne(() => UserEntity) user: UserEntity; @OneToMany(() => ImagesEntity, (join) => join.uploader) image: ImagesEntity[]; } @Entity("users activity") export class UserActivityEntity { @PrimaryGeneratedColumn("uuid") id: string; @Column({ type: "smallint", default: 0 }) bonusPoint: number; @Column({ type: "smallint", default: 0 }) purchaseCount: number; @Column({ type: "smallint", default: 0 }) productInquiryCount: number; @Column({ type: "smallint", default: 0 }) productReviewCount: number; @OneToOne(() => UserEntity) user: UserEntity; @OneToMany(() => ReviewEntity, (review) => review.reviewer) review: ReviewEntity; } 이런식으로 한 사용자 엔티티에는 프로필, 인증, 활등 에 대한 엔티티와 1:1 관계를 설정 해 두었습니다. 그리고 이제 특정 사용자에 대한 정보를 가져올 때 사용되는 메서드입니다. async findUserWithId(userId: string): Promise<UserEntity> { try { return await this.userRepository .createQueryBuilder("user") .innerJoinAndSelect("user.profile", "profile") .innerJoinAndSelect("user.auth", "auth") .innerJoinAndSelect("user.activity", "activity") .where("user.id = :id", { id: userId }) .getOneOrFail(); } catch (err) { throw new UnauthorizedException("해당 사용자아이디는 존재하지 않습니다."); } } userId 매개변수는 CommenEntity에서 상속된 id컬럼을 사용합니다. 즉 사용자 엔티티 id라고 보시면 됩니다. userRepository또한 아래처럼 사용자 엔티티의 리파지토리입니다. @InjectRepository(UserEntity) private readonly userRepository: Repository<UserEntity> 이제 본격적으로 문제에 관련된 질문을 드리자면 innerJoinAndSelect를 사용할 시 관계가 맺어진 엔티티들의 정보들을 모두 가져오게 되어서 노출이 되어서는 안될 비밀번호등 까지 보여지게 됩니다. 그래서 저는 innerJoinAndSelect대신 innerJoin으로 관계가 맺어진 엔티티들을 조인하고 select메서드로 원하는 컬럼만 선택하려했습니다. async findUserWithId(userId: string): Promise<UserEntity> { try { return await this.userRepository .createQueryBuilder("user") .select(this.select.UserInformationReturnProperty) .innerJoin("user.profile", "profile") .inneroin("user.auth", "auth") .innerJoin("user.activity", "activity") .where("user.id = :id", { id: userId }) .getOneOrFail(); } catch (err) { throw new UnauthorizedException("해당 사용자아이디는 존재하지 않습니다."); } } select안에 인수는 아래와 같습니다. UserInformationReturnProperty: [ "profile.realname", "auth.nickname", "profile.birth", "profile.gender", "auth.email", "profile.phonenumber", "auth.userType", "activity.purchaseCount", "activity.bonusPoint", "activity.productInquiryCount", "activity.productReviewCount", ] 이렇게 보여줘도 상관없을 만한 정보들로 구성하였습니다. 문제는 이렇게 하고 쿼리를 돌렸을 때 'EntityNotFoundError: Could not find any entity of type "UserEntity" matching: [object Object]'이런 오류가 납니다. 해석해보면 '일치하는 "UserEntity" 유형의 엔터티를 찾을 수 없음' 이런 뜻인거 같은데 분명히 리파지토리도 UserEntity이고 select 대신 innerJoinAndSelect를 하면 정상적으로 불러와 지는데 어떤 부분이 잘못된건지 모르겠어서 질문드립니다.
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
제로초님 entity가져오고 에러가 계속나네요... 그대로 복붙했거든요 ..
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
ormconfig.ts 에러 질문입니다.
제로초님 안녕하세요! ormconfig.ts typeorm seeding, migration 쓰강의 보다가 에러나서 질문드려요 ㅠㅠ 그리고 connection: Connection으로 주셨는데 현재 사용하지 않는거 같아서 공식문서 보니 dataSource로 바뀐것같아서 타입으로 주고 실행하는데데 타입동작하지않아요
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
typeorm migration 실행이 안 됩니다.
제로초님 안녕하세요. 강좌에 나온대로 app module에 TypeOrmModule 관련된 설정은 아래처럼 해둔 상태인데요. npx typeorm migration:create -n categoryToType 명령어를 실행시키면 ...@Module({imports: [ConfigModule.forRoot({ isGlobal: true, load: [getEnv] }),TypeOrmModule.forRootAsync({inject: [ConfigService],useFactory: async (configService: ConfigService) => {return {type: 'mysql',host: 'localhost',port: 3306,username: configService.get('DB_USERNAME'),password: configService.get('DB_PASSWORD'),database: configService.get('DB_DATABASE'),entities: [...],migrations: [__dirname + '/src/migrations/*.ts'],cli: { migrationsDir: 'src/migrations' },autoLoadEntities: true,charset: 'utf8mb4',synchronize: false,logging: true, keepConnectionAlive: true, };},}),UsersModule,WorkspacesModule,ChannelsModule,DmsModule,],controllers: [AppController],providers: [AppService,ConfigService,{ provide: 'CUSTOM_KEY', useValue: 'CUSTOM_VALUE' },UsersService,],exports: [],})export class AppModule implements NestModule {configure(consumer: MiddlewareConsumer): any {consumer.apply(LoggerMiddleware).forRoutes('*');}} Not enough non-option arguments: got 0, need at least 1 이라는 에러메시지가 나오면서 마이그레이션이 안 됩니다. typeorm 공식문서(https://typeorm.io/using-cli#create-a-new-migration) 에서도 위처럼 TypeOrmModule 설정에 cli를 설정해두거나 아니면 npx typeorm migration:create -n UserMigration -d src/user/migration 이런 식으로 하게끔 되어 있는 것 같아서 path를 지정해서 해봤는데도 같은 에러가 나옵니다. 혹시 무엇이 문제인 걸까요?
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
인터셉터를 통한 응답의 관련된 질문입니다.
안녕하세요 조현영님. 인터셉터 관련해서 궁금한게 있어 질문드립니다. 아래는 제가 만들어 놓은 인터셉터입니다. intercept(context: ArgumentsHost, next: CallHandler<any>): Observable<any> { // controller 도달 전 const req = context.switchToHttp().getRequest(); const res = context.switchToHttp().getResponse(); console.log(`Receive request from ${req.method} ${req.originalUrl}`); const now = Date.now(); return next.handle().pipe( map((data: JSON<null>) => { // controller 도달 후 console.log( `Send response from ${req.method} ${ req.originalUrl } :: time taken : ${Date.now() - now}ms`, ); return res.status(data.statusCode).setHeader("X-Powered-By", "").json({ success: true, ...data}); }), ); } 이전까지는 이런식으로 인터셉터를 구성해서 포스트맨으로 사용할 땐 문제없이 요청과 응답이 오고 갈수 있었습니다. 그런데 시험삼아서 브라우저에 서버 url을 입력후 get메서드를 사용하는 api를 사용해봤는데 응답은 잘 갔었지만 서버 콘솔에 cannot set headers 오류가 났습니다. 이 오류는 꽤나 익숙해서 응답이 두번 보내져서 그런가 싶어 인터셉터 응답을 아래처럼 바꿔보았습니다. intercept(context: ArgumentsHost, next: CallHandler<any>): Observable<any> { // controller 도달 전 const req = context.switchToHttp().getRequest(); const res = context.switchToHttp().getResponse(); console.log(`Receive request from ${req.method} ${req.originalUrl}`); const now = Date.now(); return next.handle().pipe( map((data: JSON<null>) => { // controller 도달 후 console.log( `Send response from ${req.method} ${ req.originalUrl } :: time taken : ${Date.now() - now}ms`, ); res.status(data.statusCode).setHeader("X-Powered-By", ""); return { success: true, ...data }; }), ); } 이상한게 get, delete 메서드를 사용하는 api는 return문을 거쳤을 때 응답이 잘 도달되었지만 post,patch등은 응답이 가고 계속 로딩중입니다. 계속 기다려도 이러한 상태여서 디버깅을 통해 문제를 해결하고자 return문에서 부터 계속 디버깅을 시도했습니다. 그리고 문제였던 아래 코드를 발견했습니다. return async (result, res) => { result = await this.responseController.transformToResult(result); !isResponseHandled && (await this.responseController.apply(result, res, httpStatusCode)); }; 위의 코드는 node_modules/@nestjs/core/router/router-execution-context.js 라는 파일의 174 ~ 177줄의 코드입니다. 아마 result 변수가 인터셉터에서 리턴된 값으로 사용되는 변수 같은데 그 아래 있는 코드 실행 이후 계속 응답이 닿지 않는 모습이었습니다. 그래서 저는 아래처럼 다시 수정해봤습니다. return async (result, res) => { result = await this.responseController.transformToResult(result); await this.responseController.apply(result, res, httpStatusCode); }; 이후에는 post, patch메서드의 응답이 잘 닿았지만 과연 이게 올바른 해결 방법인지는 잘 모르겠습니다. 만약 이 프로젝트를 git에서 pull, clone등을 할 때 node_modules는 .gitignore에 등록해놓아서 위 처럼 변경 사항을 불러올 수가 없어서 git에서 pull, clone하려면 npm i로 모듈들을 받은 후 계속 저런식으로 수정을 해야 되서 이 방법은 좀 아닌거 같지만 일단 임시방편으로 해놓은 상태입니다. 조현영님께서는 이렇게 어떤 문제가 해결이 안될때 node_modules를 건드려서 해결하신적이 있으신가요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
Nest서버에서 실행 컨텍스트가 뭔가요?
제로초님 안녕하세요. 좋은 강의 재밌게 잘 듣고 있습니다. 다름이 아니라 커스텀 데코레이터 만드는 것을 설명해주시면서 같은 서버로 http, websocket, rpc를 동시에 돌릴 수 있기 때문에 이 중에서 필요한 부분의 정보를 가져올 때 switchTo__ 함수를 쓸 수 있다고 하셨는데요. 그런데 제가 아는 실행 컨텍스트는 자바스크립트에서 이야기하는 실행컨텍스트밖에 없는데, nest의 실행 컨텍스트라는 개념은 어디서 나온 건지 궁금합니다. Nest에서 관련된 문서를 읽어보기는 했는데, 프로그래밍 언어 레벨에서의 실행 컨텍스트와는 별도로 그냥 nest에서 http, ws, rpc를 동시에 지원하기 위해서 만든 개념이라고 생각하면 될까요? 감사합니다.
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
connection 여러개에 대하여
현재 스케줄러를 사용해서 탈퇴된 계정을 오전 6시에 일괄적으로 처리하는 코드를 구현중입니다. 그래서 아래와같은 db와 커넥션을 맺는 함수(userLeaveRoom)를 스케줄러에 등록해서 사용하려고하는 중에 질문이 생겼습니다. // 매일 6시 삭제된지 한달 지난 계정 삭제 @Cron('0 0 06 * * *', { name: 'initAccount' }) async initAccount() { / ... / const promises = roomMembers.map((member) => this.roomsService.userLeaveRoom(member)); const results = await Promise.allSettled(promises); } 이런식으로 사용할 경우에 순간적으로 커넥션을 많이 맺게되는데 이게 문제가 없을지 궁금합니다. async userLeaveRoom(leaveMember: RoomMembers) { const queryRunner = this.connection.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { /.../ await queryRunner.commitTransaction(); } catch (err) { await queryRunner.rollbackTransaction(); throw err; } finally { await queryRunner.release(); } }
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
multer를 사용해서 사진 혹은 동영상을 업로드 할 때 url을 통해서 사진은 접근이 가능하나 동영상은 404에러가 뜹니다.
안녕하세요. 오랜만에 질문드립니다. 제가 사진이나 동영상등을 멀터를 통해서 업로드 한 후 사진과 동영상을 각각 uploads/image, uploads/video 디렉터리에 disk storage 방식으로 저장하게 하였습니다. 그리고 저장된 파일이름과 서버url을 결합시켜서 db에 저장하게 하였고 사진은 url을 통해서 접근이 가능한데 동영상은 url로 접근할 때 404에러가 뜨더라구요. 동영상이 저장된 디렉터리로 이동시 동영상 파일이 온전히 있고 동시에 재생도 가능했습니다. 인코딩등의 문제가 관련이 있을까요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
제로초님 질문드리고싶습니다. 이런문제는 왜발생한건지 파일 캡쳐합니다 도저히 이해가 안돼네요 undefined property verify
import { Injectable, ExecutionContext, HttpException, HttpStatus, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') { constructor( private readonly jwtService: JwtService, // @Inject(forwardRef(() => AdminsService)) // private readonly adminsService: AdminsService, ) { super(); } canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); const { authorization } = request.headers; if (authorization === undefined) { throw new HttpException('Token 전송 안됨', HttpStatus.UNAUTHORIZED); } //const token = authorization.replace('Bearer ', authorization); const token = authorization.replace('Bearer ', ''); //console.log(token, 'token!!!'); request.user = this.validateToken(token); return true; } validateToken(token: string) { const secretKey = process.env.SECRET ? process.env.SECRET : 'dev'; try { const data = this.jwtService.verify(token, { secret: secretKey, }); console.log(data, '11번가데이터'); return data; } catch (e) { switch (e.message) { // 토큰에 대한 오류를 판단합니다. case 'INVALID_TOKEN': case 'TOKEN_IS_ARRAY': case 'NO_USER': throw new HttpException('유효하지 않은 토큰입니다.', 401); case 'EXPIRED_TOKEN': throw new HttpException('토큰이 만료되었습니다.', 410); default: console.trace(e); // console.log('광섭짱과 함께하는 코딩공부',) throw new HttpException('서버 오류입니다.', 500); } } } } 이부분은 jwt.guard.ts 입니다 저 빨간줄에서 Trace: TypeError: Cannot read properties of undefined (reading 'verify') 이렇게 나오는데 왜 저렇게 나오는건지 도저히 모르겠네요 해당 토큰값도 잘 받아와서 verify 를 이용해 토큰 유효성 검사를 진행하려하는데 그부분에서 에러가 계속 납니다... 도와주세요
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
관계가 적용되었을 때 row를 삭제하면 관계가 적용된 row도 삭제 하게 하려면 어떻게 해야 할까요?
상품 entity와 이미지 entity가 아래 처럼 있다고 가정합니다. @Entity("products") export class ProductEntity extends CommonEntity { @IsString() @IsNotEmpty() @Column({ type: "varchar", length: 20, unique: true, nullable: false }) name: string; @IsNumber() @IsNotEmpty() @Column({ type: "int", unsigned: true, nullable: false }) price: number; @IsString() @IsNotEmpty() @Column({ type: "varchar", length: 20, nullable: false }) origin: string; @IsString() @IsNotEmpty() @Column({ type: "varchar", length: 20, nullable: false }) type: string; @IsString() @IsNotEmpty() @Column({ type: "text", nullable: true }) description: string; @Column({ type: "int", default: 50 }) quantity: number; @Column({ type: "float", default: 0.0 }) rating: float; @OneToOne(() => ImagesEntity, (image) => image.product, { cascade: true }) @JoinColumn({ name: "imageId" }) image: ImagesEntity; } @Entity("images") export class ImagesEntity extends CommonEntity { @OneToOne(() => ProductEntity, (product: ProductEntity) => product, { onDelete: "CASCADE", onUpdate: "CASCADE", }) product: ProductEntity; @Column({ type: "varchar", nullable: false, unique: true }) url: string; @ManyToOne(() => UserAuthEntity, (Join) => Join.image) @JoinColumn({ name: "uploaderId", referencedColumnName: "id" }) uploader: UserAuthEntity; @Column({ type: "enum", enum: ["product upload", "review", "inquiry"] }) uploadReason: "product upload" | "review"; } product entity에는 image라는 가상 컬럼이 존재하고 images entity에는 product라는 가상 컬럼이 존재합니다. 여기서 상품쪽 row가 지워지면 이미지쪽 row도 지워지게 하고 싶어서 각각의 가상컬럼에 casecade : true 와 onDelete: "CASCADE"를 주었습니다. 질문 1. 상품 쪽에서 지울 때 이미지 쪽에서도 지워지게 하려면 옵션을 저렇게 설정하는게 맞을까요? 질문 2. 아래는 제가 구글링을 하다가 찾게 된 코드입니다. const author = await Author.findOne({ id: '123' }); author.books.push(new Book(...)); await author.save(); 위 같은 방식이 activate record가 맞을까요? 질문 3. 위 질문이 맞다면 제가 save메서드를 호출해야 cascade가 적용 된다는 글을 봤었는데(https://velog.io/@hahaha/TypeORM-Relation) 그러면 repository pattern을 사용하면 cascade를 적용시킬 수 없는것일까요?
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
typeorm 쿼리를 사용할 때 에러가 날 수있는 가능성이 궁금합니다.
안녕하세요 조현영님. typeorm 쿼리 중에서 뭔가를 찾거나 할 때 find혹은 findOne등을 사용잖아요. 그리고 create로 생성하거나 save로 저장하고 이런 쿼리등을 사용할 때 에러가 날 수 있는 경우가 있을까요? 단 find의 where등으로 컬럼에 접근 하는 경우를 제외하고요. await을 연달아 쓰는것 대신 settled로 묶은 후reject된값을 에러 배열에 담은 다음 에러 배열의 길이가 1이상이면 InternalServerErrorException을 throw 하려 하는데 에러 처리하는 구문 때문에 함수가 비대해지는 느낌이 있어서요. 만약 에러가 날 가능성이 없다면 이 에러 처리 구문을 굳이 안넣으려 하거든요. 가능성이 있을지 궁금합니다.
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
OneToMany 관계 설정 질문드립니다.
안녕하세요 조현영님. 질문이 계속 쏟아져나오네요! 첫 질문 때 이야기 했던 사용자(UserEntity)와 이미지(ImageEntity)간의 관계는 OneToMany 관계인것에 대해 질문하려합니다. UserEntity에 image라는 컬럼을 두고 아래 처럼 관계 설정 하였습니다. // user.entity.ts @OneToMany(() => ImagesEntity, (join) => join.imageForigenKeyForUser) @JoinColumn() image?: ImagesEntity; 아래는 ImageEntity의 관계 설정입니다. // image.entity.ts @ManyToOne(() => UserEntity) imageForigenKeyForUser: string; 질문 1. mysql 워크밴치등으로 Image테이블을 보면 imageForigenKeyForUserId라는 컬럼이 생성되었습니다. 이렇게 컬럼이름 + Id가 붙은 컬럼을 가상 컬럼이라 부르는것이 맞을까요? 질문 2. 만약 위 질문이 맞다면 원래 제가 예상한것은 User테이블에 imageId라는 가상 컬럼이 생기는것을 예상했는데 User테이블에는 가상 컬럼이 존재하지가 않네요. 위 코드에서 관계 설정이 잘못된 것일까요? 제가 이전에 상품과 이미지 관계를 OneToOne으로 맺었을 때는 제가 원한대로 (JoinColumn()이 붙은쪽에 컬럼이름 + Id가 붙은 가상 컬럼이 생성됨) 관계가 형성 되었습니다. 아래 코드를 사용해서요. // product.entity.ts @OneToOne(() => ImagesEntity, (join) => join.imageForigenKeyForProduct) @JoinColumn() image: ImagesEntity; // image.entity.ts @OneToOne(() => ProductEntity) imageForigenKeyForProduct: string; 질문 3. 한쪽에서 OneToMany 관계로 시작하면 받는쪽은 ManyToOne이 맞죠? ex) @OneToOne() Image => @ManyToOne() ImageForigenKey 질문이 많은거 같은데 시간 나실 때 천천히 봐주시면 감사할거 같습니다.
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
SQL문 질문
선생님 채팅방 강퇴를 구현중인데, 강퇴한사람이 해당 방 맴버가 맞는지 그리고 강퇴권한(isOwner OR isManager)이 있는지 체크하려는 sql문을 작성중입니다. SELECT `rm`.`UserId` AS `rm_UserId`, `rm`.`RoomId` AS `rm_RoomId` FROM `room_members` `rm` WHERE `rm`.`RoomId` = ? AND `rm`.`UserId` = ? AND `rm`.`isOwner` = ? OR `rm`.`isManager` = ?이렇게 sql을 짜도 될지 모르겠습니다.roomId와 userId에 인덱싱이 걸려서 먼저 처리되고 isOwner OR isManager 둘중 하나인지 확인하려고하는데, 마지막에 or 때문에 풀쿼리들어갈까봐 걱정됩니다. 저렇게 짜도 괜찮을까요?? typeorm으론 이렇습니다. const isManager = await this.roomMembersRepository .createQueryBuilder('rm') .select('rm.UserId') .where('rm.RoomId = :RoomId AND rm.UserId = :UserId', { RoomId, UserId }) .andWhere('rm.isOwner = :isOwner OR rm.isManager = :isManager', { isOwner: true, isManager: true, }) .getOne();
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
validation pipe를 거쳐서 나온 에러의 이름을 Bad Request 대신 다른 이름으로 사용하고 싶습니다.
안녕하세요. 조현영님 강좌를 보던 중 요청 값들을 dto로 묶은 후 거기에 class-validator 객체에 원하는 validator를 설정시키고 (ex: IsString(), IsNumber() 등) 해당 validator 조건의 부합 하다면 만들어 둔 exception filter 함수 안에 조건에 따라 Validation exception 혹은 http exception 일 때 응답을 다르게 정의 한 내용을 보았습니다. 그 중 하나 거슬리는 부분이Validation exception을 통해 온 에러는 "Bad Request"가 고정이라는 것입니다. 혹시 이 값을 "Unsupported Media Type"으로 바꾸고 싶은데 방법이 있을까요?
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
제로초님 imports 모듈에러가 나서 질문드려요
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Shops } from 'src/entities/shops.entity'; import { ShopsController } from './shops.controller'; import { ShopsService } from './shops.service'; @Module({ imports: [TypeOrmModule.forFeature([Shops])], controllers: [ShopsController], providers: [ShopsService], }) export class ShopsModule {} 이렇게 작성을 하였는데 webpack 5.72.1 compiled successfully in 137 ms [Nest] 10891 - 2022. 06. 23. 오후 6:55:30 LOG [NestFactory] Starting Nest application... [Nest] 10891 - 2022. 06. 23. 오후 6:55:30 LOG [InstanceLoader] TypeOrmModule dependencies initialized +38ms [Nest] 10891 - 2022. 06. 23. 오후 6:55:30 LOG [InstanceLoader] PassportModule dependencies initialized +0ms [Nest] 10891 - 2022. 06. 23. 오후 6:55:30 ERROR [ExceptionHandler] Nest can't resolve dependencies of the ShopsService (ShopsRepository, ?). Please make sure that the argument UsersRepository at index [1] is available in the ShopsModule context. Potential solutions: - If UsersRepository is a provider, is it part of the current ShopsModule? - If UsersRepository is exported from a separate @Module, is that module imported within ShopsModule? @Module({ imports: [ /* the Module containing UsersRepository */ ] }) 이렇게 에러가 나네요.. 도저히 모르겠어서 질문드립니다.
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
데코레이터의 인자로 클래스내의 프로퍼티 혹은 메서드등을 this로 전달할 수 없나요?
안녕하세요 조현영님. 이번에는 데코레이터에 this를 전달하려는데 this에 빨간줄에러가 나서 문의드립니다. 이미지 업로드등을 하기 위해서 아래처럼 MulterProvider라는 provider를 만들고 해당 모듈에 provider에 넣어주었습니다. // multer.provider.ts import { Injectable, Logger } from "@nestjs/common"; import { MulterOptions } from "@nestjs/platform-express/multer/interfaces/multer-options.interface"; import * as fs from "fs"; import * as path from "path"; import * as multer from "multer"; @Injectable() export class MulterProvider { private readonly logger = new Logger("Multer"); createFolder(folder1: string, folder2: string) { try { this.logger.log("create uploads folder"); fs.mkdirSync(path.join(__dirname, "../../../uploads")); } catch (err) { this.logger.log("uploads folder is already exist"); } try { this.logger.log(`create ${folder1}folder into uploads foler`); fs.mkdirSync(path.join(__dirname, `../../../uploads/${folder1}`)); } catch (err) { this.logger.log(`${folder1} is already exist`); } try { this.logger.log(`create ${folder2}folder into uploads folder`); fs.mkdirSync(path.join(__dirname, `../../../uploads/${folder2}`)); } catch (err) { this.logger.log(`${folder2} is already exist`); } } storage(folder1: string, folder2: string): multer.StorageEngine { this.createFolder(folder1, folder2); return multer.diskStorage({ destination(req, file, cb) { if (file.mimetype.includes("image")) { const folderName = path.join( __dirname, `../../../uploads/${folder1}`, ); cb(null, folderName); } else { const folderName = path.join( __dirname, `../../../uploads/${folder2}`, ); cb(null, folderName); } }, filename(req, file, cb) { const ext: string = path.extname(file.originalname); const fileName = `${path.basename( file.originalname, ext, )}-${Date.now()}${ext}`; cb(null, fileName); }, }); } apply(folder1: string, folder2: string) { const result: MulterOptions = { storage: this.storage(folder1, folder2), }; return result; } } // upload.module.ts import { MulterProvider } from "src/model/upload/multer.provider"; import { TypeOrmModule } from "@nestjs/typeorm"; import { Module } from "@nestjs/common"; import { UploadService } from "../upload/services/upload.service"; import { UploadController } from "../upload/controllers/upload.controller"; import { ImagesEntity, VideosEntity } from "./entities/upload.entity"; import { UploadRepository } from "./upload.repository"; @Module({ imports: [TypeOrmModule.forFeature([ImagesEntity, VideosEntity])], controllers: [UploadController], providers: [UploadService, UploadRepository, MulterProvider], exports: [UploadRepository], }) export class UploadModule {} 그리고 이미지를 업로드 할 컨트롤러에 MulterProvider를 적용시키기 위해 아래 처럼하였습니다. @Controller("upload") export class UploadController { constructor( private readonly uploadService: UploadService, private readonly multerProvider: MulterProvider, ) {} @UseInterceptors( FileInterceptor("image", this.multerProvider.apply("image", "video")), ) 이 때 FileInterceptor() 함수의 this가 들어간 인자가 빨간색 에러가 뜨며 에러 내용은 개체가 'undefined'인거 같습니다 라고 뜹니다. 위 예시를 di하지 않고 this.multerProvider 대신 new를 사용해 클래스를 초기화 한다면 정상작동 하기는 합니다. 어찌보면 FileInterceptor에 this로 된 인자를 전달해서 데코레이터에 인자로 준거랑은 다른 맥락인거 같아서 아래처럼 테스트를 해봤는데 this에 '개체가 'undefined'인거 같습니다'라는 에러가 나는 것은 똑같습니다. private readonly te = "hello"; @Get(this.te) test() { return "hello" }