44,000원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
다음 에러나시는 분들 참고하세요: "Error: Can't add new command when connection is in closed state"
제가 아래 순서대로 진행하여 해결했습니다.https://www.npmjs.com/package/typeorm-extension 의 스크립트를 참고하였는가MySQL hosts파일에 ::1이 localhost로 바인딩되어있는 부분을 주석처리하기이게 어려우면 localhost대신 루프백주소를 넣으세요이래도 안되면 에러 코드 다시 읽어보기그래도 안되면 chatGPT한테 물어보기참고로, 강사님이 쓰는 extension이랑 최신버전이랑 사용법이 약간 다른 것 같은데 자세히는 아직 안봤지만 각자 1번은 무조건 확인하십쇼
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
런타임시 타입스크립트 인터페이스가 존재하지 않는 이유는 뭔가요??
안녕하세요. 해당 강의와는 별개의 질문입니다! 클래스와 인터페이스의 차이가 런타임이 존재하는지의 여부라고 아래 답변해주신 글을 봤었습니다.가장 큰 차이는 interface는 런타임에 없고, class는 런타임에 있는 것입니다. 런타임에 있어서 런타임에도 타입체크를 수행하길 원하면 class로 선언하면 되고, 런타임에는 없길 원하면 interface를 쓰시면 됩니다. 위 답변에서 궁금한점이 타입스크립트 인터페이스가 런타임에 없는 이유가 뭔가요??컴파일시 자바스크립트 파일에서 인터페이스가 삭제되면서 런타임 시 제외가 되는 것인가요? 자바스크립트에는 인터페이스 문법이 없다고 알고 있는데.. 문법이 없기 때문에 컴파일할 수 없어서 삭제되고 런타임에서는 제외가 되는 것인지.. 궁금해져서 조금 더 구체적으로 알고 싶습니다!
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
엔티티 경로에(절대경로와 상대경로) 의한 모듈 인식
import { Channels } from '../../entities/channel.entity'; import { Workspaces } from 'src/entities/workspace.entity'; import { DataSource } from 'typeorm'; import { Seeder, SeederFactoryManager } from 'typeorm-extension'; export default class UserSeeder implements Seeder { async run( dataSource: DataSource, factoryManager: SeederFactoryManager, ): Promise<any> { const workspaceRepository = dataSource.getRepository(Workspaces); await workspaceRepository.insert([ { id: 1, name: 'nest-practice', url: 'nest-practice', }, ]); const channelsRepository = dataSource.getRepository(Channels); await channelsRepository.insert([ { id: 1, name: '개인', WorkspaceId: 1, private: true, }, ]); } }에러 로그 BaseError: Cannot find module 'src/entities/workspace.entity' Require stack: - /Users/jinyoung/Desktop/study/nest/nest-project/src/database/seeds/create-initial-data.ts - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/locter/dist/index.cjs - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/utils/file-path.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/utils/index.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/factory/module.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/factory/manager.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/factory/index.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/index.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/data-source/options/module.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/data-source/options/index.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/data-source/index.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/cli/commands/database/create.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/cli/commands/database/index.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/cli/commands/index.js - /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/cli/index.js at ModuleLoader.loadSync (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/locter/src/loader/built-in/module/module.ts:179:23) at ModuleLoader.loadSync (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/locter/src/loader/built-in/module/module.ts:172:37) at ModuleLoader.execute (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/locter/src/loader/built-in/module/module.ts:55:31) at async prepareSeeder (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/module.js:58:39) at async runSeeders (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/module.js:111:19) at async Object.handler (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/cli/commands/seed.js:59:9) { options: { code: 'MODULE_NOT_FOUND', message: "Cannot find module 'src/entities/workspace.entity'\n" + 'Require stack:\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/src/database/seeds/create-initial-data.ts\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/locter/dist/index.cjs\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/utils/file-path.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/utils/index.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/factory/module.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/factory/manager.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/factory/index.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/seeder/index.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/data-source/options/module.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/data-source/options/index.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/data-source/index.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/cli/commands/database/create.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/cli/commands/database/index.js\n' + '- /Users/jinyoung/Desktop/study/nest/nest-project/node_modules/typeorm-extension/dist/cli/commands/index.js\n' +해당 코드에서 처음엔 Channels라는 엔티티경로를 인식해오지 못한다고 에러 로그를 받았습니다. 이거저거 해보다가 아 혹시 상대경로로 바꿔주면 인식이 될까? 해서 상대경로로 바꿔 주었더니 Channels가 인식되기 시작했습니다. 궁금한점은 처음 Import해올 당시 제가 경로를 직접입력한것이 아닌 타입스크립트가 자동으로 경로를 잡아주어 import했습니다. 하지만 어째서 타입스크립트가 인식해서 잡아준 경로에 존재하는 모듈을 찾을수 없다고 나오는지 모르겠습니다.
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
dataSource내부 환경변수 인식 불가에 따른 db연결 실패
dataSource.ts import { DataSource } from 'typeorm'; import { ChannelChats } from './src/entities/channelChat.entity'; import { ChannelMembers } from './src/entities/channelMembers.entity'; import { Channels } from './src/entities/channel.entity'; import { DMs } from './src/entities/dm.entity'; import { Mentions } from './src/entities/mention.entity'; import { Users } from './src/entities/user.entity'; import { WorkspaceMembers } from './src/entities/workspaceMembers.entity'; import { Workspaces } from './src/entities/workspace.entity'; import { ConfigService } from '@nestjs/config'; const configService = new ConfigService(); const test = configService.get('DB_HOST'); console.log(test) const dataSource = new DataSource({ type: 'mysql', host: '127.0.0.1', port: 3306, username: configService.get('DB_USER'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_NAME'), entities: [ ChannelChats, ChannelMembers, Channels, DMs, Mentions, Users, WorkspaceMembers, Workspaces, ], migrations: [__dirname + '/src/migrations/*.ts'], synchronize: false, logging: true, });출력 에러 로그 yarn seed yarn run v1.22.19 warning ../../../../package.json: No license field $ ts-node ./node_modules/typeorm-extension/dist/cli/index.js seed -d ./dataSource.ts undefined !!!!!!!!!!!!!!! Error during Data Source initialization Error: Access denied for user ''@'localhost' (using password: NO) at Packet.asError (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/mysql2/lib/packets/packet.js:728:17) at ClientHandshake.execute (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/mysql2/lib/commands/command.js:29:26) at PoolConnection.handlePacket (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/mysql2/lib/connection.js:478:34) at PacketParser.onPacket (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/mysql2/lib/connection.js:97:12) at PacketParser.executeStart (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/mysql2/lib/packet_parser.js:75:16) at Socket.<anonymous> (/Users/jinyoung/Desktop/study/nest/nest-project/node_modules/mysql2/lib/connection.js:104:25) at Socket.emit (node:events:513:28) at Socket.emit (node:domain:489:12) at addChunk (node:internal/streams/readable:324:12) at readableAddChunk (node:internal/streams/readable:297:9) { code: 'ER_ACCESS_DENIED_ERROR', errno: 1045, sqlState: '28000', sqlMessage: "Access denied for user ''@'localhost' (using password: NO)", sql: undefined } typeorm-extension seed.env NAME=development DB_HOST=localhost DB_NAME=nestdb DB_USER=root DB_PASSWORD=password!!!package.json "db:create": "ts-node ./node_modules/typeorm-extension/dist/cli/index.js db:create -d ./dataSource.ts", "db:drop": "ts-node ./node_modules/typeorm-extension/dist/cli/index.js db:drop -d ./dataSource.ts", "seed": "ts-node ./node_modules/typeorm-extension/dist/cli/index.js seed -d ./dataSource.ts", "schema:drop": "ts-node ./node_modules/typeorm/cli.js schema:drop", "schema:sync": "ts-node ./node_modules/typeorm/cli.js schema:sync", "db:migrate": "npm run typeorm migration:run -- -d ./dataSource.ts", "db:migrate:revert": "npm run typeorm migration:revert -- -d ./dataSource.ts", "db:create-migration": "npm run typeorm migration:create -- ./src/migrations/", "db:generate-migration": "npm run typeorm migration:generate -- ./src/migrations -d ./dataSource.ts"폴더구조 - node_module - src - .env - dataSource.ts - package.json 출력에러 로그에 보시면 맨위 yarn seed 기준 5번째 줄에 undefined !!!!!! 이런 로그를 확인하실수 있으실텐대 이게 process.env.DB_NAME을 해도 이렇게 나오고 configService.get('DB_NAME')을 해도 undefined로 나오는걸로 확인해본결과 현재 환경변수가 인지가 되지않는 상태라는것을 알아챈 상태까지 왔습니다. 여기서 어떤 부분을 손보면 좋을지 궁금합니다.
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
.env 파일에 3030 포트로 동일하게 작성했지만 사이트에 연결할 수 없음이 나오는데 어디 부분이 빠진 건가요?
안녕하세요. 포트 3000 에서는 사이트도 잘 뜨고 로깅도 잘 되고 있습니다.다만, 환경변수 파일에 추가한 3030 포트로는 열리지 않고 있습니다. 어디 부분을 확인해봐야 되나요??.envSECRET=제로초강의 PORT=3030 main.tsimport { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); const port = process.env.PORT || 3000; await app.listen(3000); console.log(`listening on port ${port}`); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); app.service.tsimport { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; // 서비스의 경우, 요청과 응답에 대해서는 모르기 때문에 독립적이다. // 즉, 로직을 작성하는 공간이라고 생각하면 된다. @Injectable() export class AppService { constructor(private readonly configService: ConfigService) {} getHello(): string { // process.env.PORT보단 get으로 가져오는 것이 좋다. // return this.configService.get('SECRET'); return process.env.SECRET; } } app.module.tsimport { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { LoggerMiddleware } from './middlewares/logger.middleware'; @Module({ imports: [ConfigModule.forRoot()], controllers: [AppController], providers: [AppService], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer): any { consumer.apply(LoggerMiddleware).forRoutes('*'); } } logger.moddleware.tsimport { Injectable, Logger, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { private logger = new Logger('HTTP'); use(request: Request, response: Response, next: NextFunction): void { const { ip, method, originalUrl } = request; const userAgent = request.get('user-agent') || ''; response.on('finish', () => { const { statusCode } = response; const contentLenth = response.get('content-length'); // 만약 context가 필요가 없을 경우 Logger.log가 console.log 대체이다. this.logger.log( `${method} ${originalUrl} ${statusCode} ${contentLenth} - ${userAgent} ${ip}`, ); }); next(); } }
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
핫리로딩으로 공식문서와 동일하게 적용했는데 찾을 수 없다고만 뜨는데 이유가 뭘까요??
핫리로딩으로 공식문서와 동일하게 적용했는데 찾을 수 없다고만 뜨는데 이유가 뭘까요??아래 작성 코드 확인부탁드립니다! 터미널 에러PS C:\nestStudy\slack-clone-app\a-nest> npm run start:dev > a-nest@0.0.1 start:dev > nest build --webpack --webpackPath webpack-hmr.config.js --watch Error Cannot find module 'C:\nestStudy\slack-clone-app\a-nest\webpack-hmr.config.js' Require stack: - C:\nestStudy\slack-clone-app\a-nest\node_modules\@nestjs\cli\actions\build.action.js - C:\nestStudy\slack-clone-app\a-nest\node_modules\@nestjs\cli\actions\index.js - C:\nestStudy\slack-clone-app\a-nest\node_modules\@nestjs\cli\commands\command.loader.js - C:\nestStudy\slack-clone-app\a-nest\node_modules\@nestjs\cli\commands\index.js - C:\nestStudy\slack-clone-app\a-nest\node_modules\@nestjs\cli\bin\nest.js main.tsimport { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); const port = process.env.PORT || 3000; await app.listen(3000); console.log(`listening on port ${port}`); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap(); webpack-hmr.config.jsconst nodeExternals = require('webpack-node-externals'); const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin'); module.exports = function (options, webpack) { return { ...options, entry: ['webpack/hot/poll?100', options.entry], externals: [ nodeExternals({ allowlist: ['webpack/hot/poll?100'], }), ], plugins: [ ...options.plugins, new webpack.HotModuleReplacementPlugin(), new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/], }), new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false }), ], }; }; package.json{ "name": "a-nest", "version": "0.0.1", "description": "", "author": "", "private": true, "license": "UNLICENSED", "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "start:dev-backup": "nest start --watch", "start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch", "start:debug": "nest start --debug --watch", "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "eslint": "^8.42.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.5.0", "prettier": "^2.8.8", "run-script-webpack-plugin": "^0.2.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3", "webpack": "^5.88.2", "webpack-node-externals": "^3.0.0" }, "jest": { "moduleFileExtensions": [ "js", "json", "ts" ], "rootDir": "src", "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ "**/*.(t|j)s" ], "coverageDirectory": "../coverage", "testEnvironment": "node" } }
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
채팅 서버 통신 구조 설계 및 DB 관리 방법이 궁금합니다.
안녕하세요, 사내에서 채팅 프로젝트를 진행하게 되어 강의를 신청해 수강 중에 있습니다.( 채팅은 대규모 서비스를 전제로 하고 있지만, 현재는 상담사와 고객의 1:1 상담을 먼저 진행할 예정입니다. )관련해서 두가지 질문이 있습니다!(1) 채팅 서버 통신 구조 설계: 팀원들과 함께 이야기를 나눠볼 때, Socket으로 채팅 히스토리를 조회하거나 채팅 내용을 저장할 수 있지 않을까? 라는 이야기가 나왔습니다. 강의에서 채팅 서버 통신 구조를 설계할 때, 제로초님께서는 HTTP와 Socket을 혼합해서 사용하고 계시는데요. HTTP와 Socket을 함께 사용하시는 이유가 있는지 궁금합니다. 또한, 대규모 서비스로 전환 될 것을 전제하며 실제 서비스를 설계할 때는 어떤 방식이 더 나은지 궁금합니다.(2) DB 관리 : 채팅 히스토리를 관리하는 방법을 두가지로 고민중에 있습니다. 저장 DB는 mongoDB를 생각 중에 있습니다. (A. 인메모리에 저장해두었다가 주기적으로 DB에 저장 B. 건마다 DB에 저장) 개인 적으로는 A방법을 진행했을 때, 데이터 유실이 우려되어 B방법이 더 나은 옵션이지 않을까 생각하지만, 빈번한 DB접근이 우려됩니다. 제로초님은 어떤 방식으로 관리하고 계신지 조언주시면 감사하겠습니다!
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
docker git action ec2
강의 내용과 거리가 먼 질문이지만 제가 채팅 프로젝트를 만드는데 현재 ec2에서 docker로 배포를 하면서 진행 중인데 배포시 힙 메모리 부족으로 서버가 열리지 않아 기존 t2.micro에서 t3.small로 인스턴스 유형을 변경하니 서버가 정상적으로 실행이 됩니다. 다음은 스왑을 적용한 t2.micro와 t3.small 인스턴스에서의 램 상황입니다.보시다시피 기존 t2.micro일 때 는 swap메모리의 크기와 상관없이 거의 사용하지 않는데 swap메모리는 hdd에서 끌어다 쓰다보니 swap메모리를 사용하는데 제한이 있는건가요??다음은 힙메모리 초과로 서버가 열리지 않을 때 입니다.<--- Last few GCs ---> [18:0x7f6622d90300] 21689 ms: Scavenge (reduce) 481.9 (490.6) -> 481.5 (491.1) MB, 15.1 / 0.0 ms (average mu = 0.143, current mu = 0.005) allocation failure; [18:0x7f6622d90300] 22102 ms: Mark-sweep (reduce) 482.2 (491.1) -> 481.9 (491.9) MB, 349.1 / 0.0 ms (+ 121.4 ms in 21 steps since start of marking, biggest step 21.3 ms, walltime since start of marking 503 ms) (average mu = 0.231, current mu = 0.312) <--- JS stacktrace ---> FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory npm ERR! path /home/app npm ERR! command failed npm ERR! signal SIGABRT npm ERR! command sh -c nest start --watch npm ERR! A complete log of this run can be found in: npm ERR! /root/.npm/_logs/2023-07-07T09_03_12_827Z-debug-0.log
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
배포시에 또 nest build 하는 이유가 궁금합니다.
안녕하세요!배포 강좌를 보면서 궁금한점이 생겨서 질문을 남깁니다.배포를 할때 nest build 명령어로 빌드를 하고 서버에 풀을 받아서 서버를 start를 하게 되는데요그런데 npm run start:dev를 하면 개발시에 dist 파일이 생기는데 왜 한번 더 빌드를 해주는건가요?
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
로그인 시 쿠키세션에 대하여 질문있습니다.
안녕하세요. 강사님 좋은 강의 감사드립니다.로그인 시 인증 방법에 대하여 질문있습니다. 1. 로그인 할때 session 식별자를 쿠키와 함께 클라이언트에 전해주는 코드가 어떤 것인지 궁금합니다.로그인시 local-strategy가 실행되면 맨 마지막에 serializeUser에 user.id만 세션에 저장해 놓고 필요할때마다deserializeUser에서 userid를 뽑아올 수 있잖아요? 여기까지는 이해했습니다.그런데 로그인을 성공하면 어떤 코드에서 cookie가 session식별자를 프론트에 전달해주는지 궁금합니다.main.ts에서 app.use(cookieParser()); app.use( session({ resave: false, saveUninitialized: false, secret: process.env.COOKIE_SECRET, cookie: { httpOnly: true, }, }), ); app.use(passport.initialize()); app.use(passport.session());이 코드를 설정했기 때문에 로컬 전략 후 자동으로 쿠키를 통해 세션을 프론트에 전달해주는 것인지 궁금합니다.2. 웹이 아닌 모바일 앱에서 인증 시 백앤드 서버쪽에서 express-session과 passport를 사용하여 인증하는 것이 일반적인지 궁금합니다.모바일은 쿠키가 없기 때문에 쿠키세션은 사용하지 못하더라도 쿠키말고 body에 session 식별자를 보내주어서인증이 가능하지 않을까 라는 의문이 들었습니다.구글 검색결과 "된다 or 안 된다. 모바일앱에서는 passport나 session을 사용하지 않는 것이 좋다." 라는 의견이 있어서 헷갈립니다.모바일앱에서는 passport와 session을 사용하지 않고 jwt를 사용하는 것이 일반적인지 의견을 듣고싶습니다.
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
swagger 404
문서와 동일하고 동영상과 동일하게 작성했습니다. npm install --save @nestjs/swagger 그런데 404가 뜨네요... 원인을 잘 모르겠습니다. import { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';import * as process from 'process';import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';declare const module: any;async function bootstrap() {const app = await NestFactory.create(AppModule);const port = process.env.PORT;await app.listen(port);console.log(`listening on port ${port}`);const config = new DocumentBuilder().setTitle('Sleact Api').setDescription('Sleact 개발을 위한 API 문서').setVersion('1.0').addTag('sleact').build();const document = SwaggerModule.createDocument(app, config);SwaggerModule.setup('api', app, document);if (module.hot) {module.hot.accept();module.hot.dispose(() => app.close());}}bootstrap();
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
fe &#x60;내 정보 조회&#x60;시 프론트에서 데이터 불러오지 못합니다.
조현영님 안녕하세요. 프론트, 백엔드 강좌를 잘 보고있습니다! 현영님의 강좌에서 useSwr를 사용하는법을 배우고, 백엔드도 구현해보고자 하여, 백엔드도 수강하게 되었습니다. 회원가입과 로그인을 하였을경우 정상적으로 작동하여, 로그인을 하였을경우, 프론트에게 쿠키값을 제대로 넘겨주고있지만, response 데이터를 받지 못하고있는데 원인을 알 수가 없습니다. front에서 로그인 하였을시 나오는 콘솔data user: {data: '', status: 200, statusText: 'OK', headers: AxiosHeaders, config: {…}, …} installHook.js:342 data user: {data: '', status: 200, statusText: 'OK', headers: AxiosHeaders, config: {…}, …} fetcher.ts:6 response: {data: '', status: 200, statusText: 'OK', headers: AxiosHeaders, config: {…}, …} index.tsx:91 back에서 로그인 request받았을때, 응답하는 콘솔[Nest] 69849 - 06/28/2023, 10:09:06 AM LOG [HTTP] GET /api/users 200 undefined - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ::1 query: SELECT DISTINCT `distinctAlias`.`Users_id` AS `ids_Users_id` FROM (SELECT `Users`.`id` AS `Users_id`, `Users`.`email` AS `Users_email`, `Users`.`nickname` AS `Users_nickname`, `Users__Users_Workspaces`.`id` AS `Users__Users_Workspaces_id`, `Users__Users_Workspaces`.`name` AS `Users__Users_Workspaces_name`, `Users__Users_Workspaces`.`url` AS `Users__Users_Workspaces_url`, `Users__Users_Workspaces`.`createdAt` AS `Users__Users_Workspaces_createdAt`, `Users__Users_Workspaces`.`updatedAt` AS `Users__Users_Workspaces_updatedAt`, `Users__Users_Workspaces`.`deletedAt` AS `Users__Users_Workspaces_deletedAt`, `Users__Users_Workspaces`.`OwnerId` AS `Users__Users_Workspaces_OwnerId` FROM `users` `Users` LEFT JOIN `workspacemembers` `Users_Users__Users_Workspaces` ON `Users_Users__Users_Workspaces`.`UserId`=`Users`.`id` LEFT JOIN `workspaces` `Users__Users_Workspaces` ON `Users__Users_Workspaces`.`id`=`Users_Users__Users_Workspaces`.`WorkspaceId` AND (`Users__Users_Workspaces`.`deletedAt` IS NULL) WHERE ( (`Users`.`id` = ?) ) AND ( `Users`.`deletedAt` IS NULL )) `distinctAlias` ORDER BY `Users_id` ASC LIMIT 1 -- PARAMETERS: [9] query: SELECT `Users`.`id` AS `Users_id`, `Users`.`email` AS `Users_email`, `Users`.`nickname` AS `Users_nickname`, `Users__Users_Workspaces`.`id` AS `Users__Users_Workspaces_id`, `Users__Users_Workspaces`.`name` AS `Users__Users_Workspaces_name`, `Users__Users_Workspaces`.`url` AS `Users__Users_Workspaces_url`, `Users__Users_Workspaces`.`createdAt` AS `Users__Users_Workspaces_createdAt`, `Users__Users_Workspaces`.`updatedAt` AS `Users__Users_Workspaces_updatedAt`, `Users__Users_Workspaces`.`deletedAt` AS `Users__Users_Workspaces_deletedAt`, `Users__Users_Workspaces`.`OwnerId` AS `Users__Users_Workspaces_OwnerId` FROM `users` `Users` LEFT JOIN `workspacemembers` `Users_Users__Users_Workspaces` ON `Users_Users__Users_Workspaces`.`UserId`=`Users`.`id` LEFT JOIN `workspaces` `Users__Users_Workspaces` ON `Users__Users_Workspaces`.`id`=`Users_Users__Users_Workspaces`.`WorkspaceId` AND (`Users__Users_Workspaces`.`deletedAt` IS NULL) WHERE ( (`Users`.`id` = ?) ) AND ( `Users`.`deletedAt` IS NULL ) AND ( `Users`.`id` IN (9) ) -- PARAMETERS: [9] getUsers user: Users { id: 9, email: 'sinde530@naver.com', nickname: '이카자', Workspaces: [] } [Nest] 69849 - 06/28/2023, 10:09:18 AM LOG [HTTP] GET /api/users 200 undefined - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ::1 코드들// user.controller.ts import { Body, Controller, Get, Post, Req, Res, UseGuards, UseInterceptors, } from '@nestjs/common'; import { ApiCookieAuth, ApiOperation, ApiResponse, ApiTags, } from '@nestjs/swagger'; import { LocalAuthGuard } from 'src/auth/local-auth.guard'; import { LoggedInGuard } from 'src/auth/logged-in.guard'; import { NotLoggedInGuard } from 'src/auth/not-logged-in.guard'; import { User } from 'src/common/decoraters/users.decorater'; import { UserDto } from 'src/common/dto/user.dto'; import { UndefinedToNullInterceptor } from 'src/common/interceptors/undefinedToNull.interceptor'; import { Users } from 'src/entities/Users'; import { SignUpRequestDto } from './dto/signup.request.dto'; import { UsersService } from './users.service'; @UseInterceptors(UndefinedToNullInterceptor) @ApiTags('Users') @Controller('api/users') export class UsersController { constructor(private usersService: UsersService) {} @ApiResponse({ status: 200, description: '성공', type: UserDto, }) @ApiResponse({ status: 500, description: '서버 에러', }) @ApiCookieAuth('connect.sid') @ApiOperation({ summary: '내 정보 조회' }) @Get() async getUsers(@User() user: Users) { console.log('getUsers user:', user); return user || false; } @ApiOperation({ summary: '회원가입' }) @UseGuards(NotLoggedInGuard) @Post('signup') async signup(@Body() data: SignUpRequestDto) { await this.usersService.postUsers(data.email, data.nickname, data.password); } @ApiResponse({ status: 200, description: '로그인 성공', type: UserDto, }) @ApiResponse({ status: 500, description: '서버 에러', }) @ApiOperation({ summary: '로그인' }) @UseGuards(LocalAuthGuard) @Post('signin') async signin(@User() user: Users) { return user; } @ApiOperation({ summary: '로그아웃' }) @UseGuards(LoggedInGuard) @Post('logout') logOut(@Req() request, @Res() response) { request.logOut(); response.clearCookie('connect.sid', { httpOnly: true }); response.send('ok'); } } // SignIn.tsx import axios from 'axios'; import { useCallback, useState } from 'react'; import useInput from 'src/hooks/useInput'; import fetcher from 'src/utils/fetcher'; import useSWR from 'swr'; import { Button, Container, Form, InfoBox, Input, LoginInfoBox, LoginLink, LoginText, Logo, RegisterBox, RegisterErrorText, RegisterText, SubContainer, WelcomeBox, WelcomeText, WrapperImageLogo, } from './styled'; export default function SignIn() { const { data, error, mutate } = useSWR( 'http://localhost:3090/api/users', fetcher, ); const [email, onChangeEmail] = useInput(''); const [password, onChangePassword] = useInput(''); const emailRegex = /^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/; const [errors, setErrors] = useState({ email: false, password: false, invalidEmail: false, }); const [presentEmail, setPresentEmailError] = useState(false); const [loginError, setLoginError] = useState(false); const Image = 'https://dummyimage.com/600x400/000/fff'; const handleSubmit = useCallback( async (e: any) => { e.preventDefault(); const newErrors = { email: !email, password: !password, invalidEmail: !!(email && !emailRegex.test(email)), }; setPresentEmailError(false); setErrors(newErrors); setLoginError(false); if (Object.values(newErrors).some((error) => error)) { return; } try { await axios .post( 'http://localhost:3090/api/users/signin', { email, password, }, { withCredentials: true }, ) .then(() => { mutate(); }) .catch((error) => { setPresentEmailError(error.response?.data); setLoginError(error); }); } catch (error) { console.error(error); } }, [email, password, mutate], ); console.log('data user:', data); if (error) console.log('error:', error); // if (!error && userData) { // console.log('로그인됨', userData); // return <Navigate to="/workspace/purrfect-chat/channel/general" />; // } return ( <Container> <SubContainer> <WrapperImageLogo> <Logo src={Image} alt="error" /> </WrapperImageLogo> <RegisterBox> <WelcomeBox> <WelcomeText>Welcome to Purrfect Chat!</WelcomeText> </WelcomeBox> <Form> <InfoBox> {errors.email && ( <RegisterErrorText> 이메일을 입력해 주세요. </RegisterErrorText> )} {errors.invalidEmail && ( <RegisterErrorText> 이메일 형식을 입력해 주세요. </RegisterErrorText> )} {loginError && ( <RegisterErrorText> 이메일 또는 비밀번호가 일치하지 않습니다. </RegisterErrorText> )} {!errors.email && !errors.invalidEmail && !presentEmail && ( <RegisterText>이메일</RegisterText> )} <Input name="email" type="text" value={email} onChange={onChangeEmail} /> </InfoBox> <InfoBox> {errors.password && ( <RegisterErrorText> 비밀번호를 입력해 주세요. </RegisterErrorText> )} {loginError && ( <RegisterErrorText> 이메일 또는 비밀번호가 일치하지 않습니다. </RegisterErrorText> )} {!errors.password && !loginError && ( <RegisterText>비밀번호</RegisterText> )} <Input name="password" type="password" autoComplete="true" value={password} onChange={onChangePassword} /> </InfoBox> <InfoBox> <Button type="button" onClick={handleSubmit}> 로그인 </Button> </InfoBox> <LoginInfoBox> <LoginText>아직 회원이 아니신가요?</LoginText> <LoginLink to="/signup"> 회원가입 하러가기 </LoginLink> </LoginInfoBox> </Form> </RegisterBox> </SubContainer> </Container> ); } // fetcher.ts import axios from 'axios'; const fetcher = async (url: string) => { try { const response = await axios.get(url, { withCredentials: true }); console.log('response:', response); return response; } catch (error: any) { throw new Error(error.response?.data); } }; export default fetcher; back/main.ts import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import cookieParser from 'cookie-parser'; import session from 'express-session'; import passport from 'passport'; import { AppModule } from './app.module'; import { HttpExceptionFilter } from './httpException.filter'; declare const module: any; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalFilters(new HttpExceptionFilter()); app.useGlobalPipes( new ValidationPipe({ transform: true, }), ); app.enableCors({ origin: true, credentials: true, }); const config = new DocumentBuilder() .setTitle('HTTP API ') .setDescription('개발 API 문서입니다.') .setVersion('1.0') .addCookieAuth('connect.sid') .build(); const documnet = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, documnet); app.use(cookieParser()); app.use( session({ resave: false, saveUninitialized: false, secret: process.env.COOKIE_SECRET, cookie: { httpOnly: true, }, }), ); app.use(passport.initialize()); app.use(passport.session()); const port = process.env.PORT || 3090; await app.listen(port); console.log(`listening on port ${port}`); if (module.hot) { module.hot.accept(); module.hot.dispose(() => app.close()); } } bootstrap();
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
npm run db:create
import { DataSource } from 'typeorm'; import dotenv from 'dotenv'; import { ChannelChats } from './src/entities/ChannelChats'; import { ChannelMembers } from './src/entities/ChannelMembers'; import { Channels } from './src/entities/Channels'; import { DMs } from './src/entities/DMs'; import { Mentions } from './src/entities/Mentions'; import { Users } from './src/entities/Users'; import { WorkspaceMembers } from './src/entities/WorkspaceMembers'; import { Workspaces } from './src/entities/Workspaces'; dotenv.config(); const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: process.env.DB_USERNAME, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, entities: [ ChannelChats, ChannelMembers, Channels, DMs, Mentions, Users, WorkspaceMembers, Workspaces, ], migrations: [__dirname + '/src/migrations/*.ts'], charset: 'utf8mb4_general_ci', synchronize: false, logging: true, }); export default dataSource; 에러!Error: Can't add new command when connection is in closed stateat Connection._addCommandClosedState (/Users/minchankim/Desktop/study/nestToy/node_modules/mysql2/lib/connection.js:148:17)at Connection.end (/Users/minchankim/Desktop/study/nestToy/node_modules/mysql2/lib/connection.js:855:26)at Query.onResult (/Users/minchankim/Desktop/study/nestToy/node_modules/typeorm-extension/dist/database/driver/mysql.js:27:28)at Connection._notifyError (/Users/minchankim/Desktop/study/nestToy/node_modules/mysql2/lib/connection.js:228:17)at Connection._handleFatalError (/Users/minchankim/Desktop/study/nestToy/node_modules/mysql2/lib/connection.js:167:10)at Connection._handleNetworkError (/Users/minchankim/Desktop/study/nestToy/node_modules/mysql2/lib/connection.js:180:10)at Socket.emit (node:events:513:28)at Socket.emit (node:domain:489:12)at emitErrorNT (node:internal/streams/destroy:151:8)at emitErrorCloseNT (node:internal/streams/destroy:116:3) {fatal: true}계속 이렇게 나오는데 npm run db:create데이터베이스 정보확인 및 dotenv도 재설치 해봤는데 계속 안되서 질문드립니다!버전에 문제가 있나 싶어서 강사님 package.json에서 그대로 불러왔습니다!
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
Entity nullable
강사님 강의 다 수강 이후 코드를 하나씩 뜯어보며 이해하던 중 엔티티에서 컬럼의 속성 값에 대한 의문ㅇ이 생겨 질문 드립니다. 강사님의 ChannelChats.entity에서 이와 같이 userId와 channelId가 nullable true로 설정이 되어있는데 널값을 허용하는 이유가 궁금하여 질문 드립니다. 그리고 또 다른 질문이 있는데 아래에서 m:1 관계 설정 후 joinColumn만으로도 테이블에 컬럼이 추가되는 걸로 알고 있는데 위에서 Column데코레이터로 다시 한 번 만들어준 것은 후에 select과 같은 요청시 joinColumn만으로 생성된 컬럼 값은 안넘어와서 column 데코레이터를 다시 한 번 사용해준 것이라고 이해해도 괜찮을까요??
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
nest-morgan 패키지 사용
안녕하세요! 강의 마지막 부분에 nest-morgan 패키지를 사용하면 된다고 하셨는데요. npm 문서를 확인해보니 해당 패키지가 deprecated 되어 있다고 명시돼 있습니다. 혹시 여전히 해당 패키지를 사용하고 계신지 혹은 다른 로그 패키지를 사용하고 계신지 궁금합니다!
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
외부 REST API 사용시 보안관련한 문제
안녕하세요~ 제가 현재 Nestjs 와 React로 구성한 app애서 추가로 다른 app의 API를 사용해야되는 상황인데요. API키와SECRET을 생성하고 이를 이용하여 호출하고 있는데, 이 때 클라이언트에서 요청을 한 내용을 보면 이 Authentication 관련된 토큰들이 다 노출되서 고민입니다.정확히 말하면 이건 백엔드 관련도 아니라 프론트쪽이긴한데요.. 프론트에서 다른 앱의 Restful API를 사용하여 요청할때 사용되는 토큰을 보안적으로 안전하게 숨길 수 있는 방법이 있을까요?
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
param과 queryString차이
파라미터로 id나 URL 등 값 받아올 때와 쿼리스트링으로 받아올 때 파라미터로도 perPage나 page 등 받아올 수 있는데 어떤 차이로 파라미터와 쿼리스트링을 나누어서 사용해야 할지 질문드립니다.단순히 파라미터는 http 요청시 한 번 정해지면 고정되는 값에 사용하고 쿼리스트링은 매 요청마다 변하는 값에 사용한다고 생각이 드는데 맞을까요??
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
createWorkspaceMembers 도 transaction 걸어줘야 하나요?
안녕하세요 이장우 닮은 제로초 선생님,// workspaces.service.ts async createWorkspaceMembers(url: string, email: string) { const workspace = await this.workspacesRepository.findOne({ where: { url }, relations: ['Channels'], }); // queryBuilder 버전 this.workspacesRepository .createQueryBuilder('workspaces') // typeorm은 innerJoin 하면 join한 테이블은 가져오지 않는데, // innerJoinAndSelect을 하면 join한 테이블을 다 가져온다. .innerJoinAndSelect('workspaces.Channels', 'channels') .getOne(); const user = await this.usersRepository.findOne({ where: { email } }); if (!user) return null; const workspaceMember = new WorkspaceMembers(); workspaceMember.WorkspaceId = workspace.id; workspaceMember.UserId = user.id; await this.workspaceMembersRepository.save(workspaceMember); const channelMember = new ChannelMembers(); channelMember.ChannelId = workspace.Channels.find( (v) => v.name === '일반', ).id; channelMember.UserId = user.id; await this.channelMembersRepository.save(channelMember); }강의에서 위와 같이 createWorkspaceMembers 를 작성하셨는데, 여기도 transaction을 걸어주는게 낫겠죠? 걸어주는게 맞다면, 아래와 같이 하면 되나요? async createWorkspaceMembers(url: string, email: string) { const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { const workspace = await queryRunner.manager.findOne(Workspaces, { where: { url }, relations: ['Channels'], }); const user = await queryRunner.manager.findOne(Users, { where: { email } }); if (!user) return null; const workspaceMember = new WorkspaceMembers(); workspaceMember.WorkspaceId = workspace.id; workspaceMember.UserId = user.id; await queryRunner.manager.save(workspaceMember); const channelMember = new ChannelMembers(); channelMember.ChannelId = workspace.Channels.find( (v) => v.name === '일반', ).id; channelMember.UserId = user.id; await queryRunner.manager.save(channelMember); await queryRunner.commitTransaction(); } catch (error) { console.error(error); await queryRunner.rollbackTransaction(); } finally { await queryRunner.release(); } }
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
ENOENT: no such file or directory, stat \public\index.html
안녕하세요. 강사님지금 sleact db를 생성하였고 table도 다 생성된 상태입니다.nest-typeorm 폴더와 front 폴더 를 둘 다 실행시키면 api/users api를 요청할 때ENOENT: statusCode":404, no such file or directory, stat \public\index.html'이러한 에러가 발생합니다. middlewares/frontend.middleware.ts 파일의 경로가 잘못되어 있나 해서 res.sendFile( path.join(__dirname, '..', '..', '..', 'public', 'index.html'), );를 res.sendFile(path.join(__dirname, '../', 'public', 'index.html'));로 바꿔주었더니api 로그인 페이지로 가지 않고 바로 workspace로 가집니다.response도 html파일로 옵니다. (어떠한 api를 요청해도 마찬가지입니다.) 어떤 부분에서 잘못되었는지 감이 안 잡히네요...힌트라도 주시면 감사하겠습니다!
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
ExceptionsHandler 에러가 발생합니다.
class-transformer 도 같이 설치후 요청을 날려보면 아래와 같은 의도하지 않은 에러미세지가 출력됩니다.{ "statusCode": 500, "message": "Internal server error" }또 동시에 터미널에 아래와 같은 에러 로그가 출력됩니다.[Nest] 8570 - 2023. 05. 27. 오전 10:48:03 ERROR [ExceptionsHandler] data and salt arguments required Error: data and salt arguments required at Object.hash (/Users/apple/Documents/sideProjects2/sleactNestJS/node_modules/bcrypt/bcrypt.js:137:17) at /Users/apple/Documents/sideProjects2/sleactNestJS/node_modules/bcrypt/promises.js:29:12 at new Promise (<anonymous>) at Object.module.exports.promise (/Users/apple/Documents/sideProjects2/sleactNestJS/node_modules/bcrypt/promises.js:20:12) at Object.hash (/Users/apple/Documents/sideProjects2/sleactNestJS/node_modules/bcrypt/bcrypt.js:133:25) at UsersService.postUsers (/Users/apple/Documents/sideProjects2/sleactNestJS/dist/main.js:426:55) at processTicksAndRejections (node:internal/process/task_queues:96:5) at async UsersController.join (/Users/apple/Documents/sideProjects2/sleactNestJS/dist/main.js:1247:9)ChatGPT에 문의해보니 bcrypt에러 같다고 하는데, bcrypt는 수정하지 않았고 문제도 없어보입니다. 제가 강의를 보며 수정한 코드는 4군데 입니다.users.service.ts 에 기존 Exception 코드 날리기async postUsers(email: string, nickname: string, password: string) { const user = await this.usersRepository.findOne({ where: { email } }); if (user) { throw new UnauthorizedException('이미 존재하는 사용자입니다'); }Users.ts entity에 Validation 추가하기 @IsEmail() @ApiProperty({ example: 'kim@gmail.com', description: '이메일', }) @Column('varchar', { name: 'email', unique: true, length: 30 }) email: string; @IsString() @IsNotEmpty() @Column('varchar', { name: 'nickname', length: 30 }) nickname: string; @IsString() @IsNotEmpty() @Column('varchar', { name: 'password', length: 100, select: false }) password: string; main.ts에 useGlobalPipes 꼽기app.useGlobalPipes(new ValidationPipe()); httpException.filter.ts response status 수정하기const err = exception.getResponse() as | { message: any; statusCode: number } | { error: string; statusCode: 400; message: string[] }; if (typeof err !== 'string' && err.statusCode === 400) { return response.status(status).json({ success: false, code: status, data: err.message, }); } response.status(status).json({ success: false, code: status, data: err.message, }); } }공식문서도 보았는데, 해결이 안되어서 질문 남깁니다.