묻고 답해요
130만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결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를 요청해도 마찬가지입니다.) 어떤 부분에서 잘못되었는지 감이 안 잡히네요...힌트라도 주시면 감사하겠습니다!
-
미해결따라하며 배우는 NestJS
NotFoundException
컨트롤러와 서비스에 NotFoundException을 둘다 작성하셨는데 혹시 필요없는 로직이 아닌지 생각됩니다.컨트롤러에서 데이터를 받았는데 서비스로 데이터가 안넘어 갈일은 없다고 생각되서요꼭 필요한 코드인지 의문입니다.
-
미해결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, }); } }공식문서도 보았는데, 해결이 안되어서 질문 남깁니다.
-
미해결따라하며 배우는 NestJS
도와주세요 ㅠㅠ!!
안녕하세요. 현재 typeorm 버전을 0.2로 낮춰서 진행하려고 했지만,아래 오류가 발생하여 진도를 나가지 못하고 있습니다! 왜 자꾸 파이프를 못 찾는다고 나오는지 검색해도 안되서 글 등록합니다 ㅠㅠ 도와주세요! [Nest] 19044 - 2023. 05. 25. 오후 10:31:03 ERROR [ExceptionHandler] Cannot read properties of undefined (reading '__pipes__') TypeError: Cannot read properties of undefined (reading '__pipes__') at C:\nestStudy\nestjs-board-new-app\node_modules\@nestjs\core\scanner.js:147:152 at Array.forEach (<anonymous>) at C:\nestStudy\nestjs-board-new-app\node_modules\@nestjs\core\scanner.js:147:39 at Array.forEach (<anonymous>) at DependenciesScanner.reflectInjectables (C:\nestStudy\nestjs-board-new-app\node_modules\@nestjs\core\scanner.js:146:27) at DependenciesScanner.reflectDynamicMetadata (C:\nestStudy\nestjs-board-new-app\node_modules\@nestjs\core\scanner.js:124:14) at C:\nestStudy\nestjs-board-new-app\node_modules\@nestjs\core\scanner.js:114:18 at Array.forEach (<anonymous>) at DependenciesScanner.reflectControllers (C:\nestStudy\nestjs-board-new-app\node_modules\@nestjs\core\scanner.js:112:21) at DependenciesScanner.scanModulesForDependencies (C:\nestStudy\nestjs-board-new-app\node_modules\@nestjs\core\scanner.js:84:18)
-
미해결따라하며 배우는 NestJS
auth.service.ts에서 오류가 납니다
auth.service.ts username에서 에러가 납니다.에러내용은 다음과 같습니다. { username: string; }' 형식의 인수는 'FindOneOptions<User>' 형식의 매개 변수에 할당될 수 없습니다.개체 리터럴은 알려진 속성만 지정할 수 있으며 'FindOneOptions<User>' 형식에 'username'이(가) 없습니다.ts(2345)
-
미해결따라하며 배우는 NestJS
레파지토리 버전 다운 후 포스트맨 POST 호출시 에러 도와주세요 ㅠㅠ..
안녕하세요! 아래 에러 원인 파악이 힘들어서 도움을 요청드립니다 ㅠㅠimport { EntityRepository, Repository } from 'typeorm'; import { Board } from './board.entity'; import { BoardStatus } from './board-status.enum'; import { CreateBoardDto } from './dto/creaate-board.dto'; @EntityRepository(Board) export class BoardRepository extends Repository<Board> { async createBoard(createBoardDto: CreateBoardDto): Promise<Board> { const { title, description } = createBoardDto; const board = this.create({ title, description, status: BoardStatus.PUBLIC, }); await this.save(board); return board; } } [Nest] 4284 - 2023. 05. 23. 오전 1:24:20 ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'create') TypeError: Cannot read properties of undefined (reading 'create') at BoardRepository.Repository.create (C:\nestStudy\nestjs-board-app\src\repository\Repository.ts:99:29) at BoardRepository.createBoard (C:\nestStudy\nestjs-board-app\src\boards\board.repository.ts:11:24) at BoardsService.createBoard (C:\nestStudy\nestjs-board-app\src\boards\boards.service.ts:42:33) at BoardsController.createBoard (C:\nestStudy\nestjs-board-app\src\boards\boards.controller.ts:27:31) at C:\nestStudy\nestjs-board-app\node_modules\@nestjs\core\router\router-execution-context.js:38:29 at processTicksAndRejections (node:internal/process/task_queues:95:5) at C:\nestStudy\nestjs-board-app\node_modules\@nestjs\core\router\router-execution-context.js:46:28 at C:\nestStudy\nestjs-board-app\node_modules\@nestjs\core\router\router-proxy.js:9:17 왜 자꾸 create를 읽지 못한다고 하는건가요??여기서 어디를 수정해야되는지 알려주세요 ㅠㅠ 감사합니다 ㅠㅠ
-
미해결따라하며 배우는 NestJS
레퍼지토리 문제..해결하신 분 도와주세요 ㅠㅠ..
강의에서 나오는 버전이 달라서 생기는 문제로 일단 파악은 되었지만 인터넷등 댓글에 있는 방법으로 시도해봤지만, 저는 해결되지 않아서 질문글 남깁니다 ㅠㅠ 우선 제가 마지막으로 했던 방법은 BoardsModule 파일에서 forFeature([BoardRepository->Board로 변경하고@Module({ imports: [TypeOrmModule.forFeature([Board])], controllers: [BoardsController], providers: [BoardsService, BoardRepository], }) export class BoardsModule {} BoardService 파일에서 @InjecRepository(BoardRepository)를 Board entity로 변경하면 된다는 글을 보고 따라했지만,@Injectable() export class BoardsService { constructor( @InjectRepository(Board) private boardRepository: BoardRepository, ) {} 포스트맨으로 직접 실행하기전에는 서버가 잘 실행되도, 포스트맨으로 호출하면 아래와 같은 오류가 나옵니다..[Nest] 14464 - 2023. 05. 23. 오전 12:51:47 ERROR [ExceptionsHandler] this.boardRepository.createBoard is not a function TypeError: this.boardRepository.createBoard is not a function at BoardsService.createBoard (C:\nestStudy\nestjs-board-app\src\boards\boards.service.ts:42:33) at BoardsController.createBoard (C:\nestStudy\nestjs-board-app\src\boards\boards.controller.ts:27:31) at C:\nestStudy\nestjs-board-app\node_modules\@nestjs\core\router\router-execution-context.js:38:29 at processTicksAndRejections (node:internal/process/task_queues:95:5) at C:\nestStudy\nestjs-board-app\node_modules\@nestjs\core\router\router-execution-context.js:46:28 at C:\nestStudy\nestjs-board-app\node_modules\@nestjs\core\router\router-proxy.js:9:17 BoardRepository가 제대로 주입되지 않은 것 같은데 이후 해결방법을 도와주세요 ㅠㅠ 삽질을 그만 멈추고 싶습니다..
-
미해결따라하며 배우는 NestJS
auth.service.ts에서 오류가 납니다
ㅇ에러내용은 다음과 같습니다.'{ username: string; }' 형식의 인수는 'FindOneOptions<User>' 형식의 매개 변수에 할당될 수 없습니다.개체 리터럴은 알려진 속성만 지정할 수 있으며 'FindOneOptions<User>' 형식에 'username'이(가) 없습니다.ts(2345)