inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

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

fe `내 정보 조회`시 프론트에서 데이터 불러오지 못합니다.

해결된 질문

519

성은김

작성한 질문수 1

0

조현영님 안녕하세요. 프론트, 백엔드 강좌를 잘 보고있습니다! 현영님의 강좌에서 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();

node.js express typeorm @react react-vite

답변 1

1

제로초(조현영)

async signin(@User() user: Users) {
    console.log('user', user);
    return user;
  }

하고 user 정보가 백엔드 콘솔에 뜨는지 봐보세요.

0

성은김

어지간한 부분들은 전부 콘솔로 걸어 확인 후에 정말 이해가 안되어서 여쭤봅니다!....

해당 콘솔 데이터입니다.

user: { id: 9, email: 'sinde530@naver.com'}

0

제로초(조현영)

네트워크탭에서 로그인 요청의 response에도 데이터가 비어있나요?

0

성은김

답변 감사합니다!.
정확하게 어떤 답변을 드려야하는지 애매하여서 사진들 첨부드립니다.
이해 부탁드립니다!

imageimage

 

users관련

imageimage

로그인 하였을때.

fetcher에서 가져오는 콘솔이름은 response입니다.

import axios from 'axios';

const fetcher = async (url: string) => {
    try {
        const response = await axios.get(url, { withCredentials: true });
        console.log('response:', response);
        return response.data;
    } catch (error: any) {
        throw new Error(error.response?.data);
    }
};

export default fetcher;

image

0

제로초(조현영)

로그인 시 비밀번호를 틀린 경우에도 저기 response에 비밀번호가 틀렸습니다라는 메시지가 없나요?

지금 코드는 정상적인것같은데 응답이 안 가네요

0

성은김

이메일 or 비밀번호 틀렸을시 이렇게 나옵니다!
해당 문제는 http Exception에서 잘못 내려주고 있어서 그런걸까요?

image

0

제로초(조현영)

httpException은 에러 시에만 동작하고 데이터를 잘 내려주고 있으니 문제없습니다.

성공 시에 데이터가 안 가는 게 문제인데 interceptor쪽 봐야할 것 같습니다.

0

성은김

앗 감사합니다. 공식문서보고 조금 변형을 시켜서 진행을 하였더니 데이터가 출력이 되지 않았던거였습니다. 저의 코드에선 항상 undefined값을 가지게 되어 그런 현상이 발생된거였습니다.

 

트러블슈팅 메모메모..

강의자료는 어디서 다운받나요?

0

116

4

질문 있습니다.

0

300

3

(강의 5:42 질문) providers를 통한 여러 개의 인스턴스 생성 & exports 통한 싱글톤 생성

0

154

2

코드 편집기 확장 프로그램

0

209

2

(질문)비밀 저장소에 접근하기 위한 인증 정보는 로컬 .env에 저장하는지?

0

147

2

(질문)외부 저장소를 통한 환경변수 불러오기 비동기 질문

0

170

3

로그인을 해도 LoggedInGuard쪽에서 false값이 나옵니다.

0

152

2

로그인방법이 고민됩니다.

0

191

2

yarn seed 명령어 실행 시 데이터 삽입 안됨

0

297

4

yarn run db:create 시에 발생하는 데코레이터 오류

0

242

2

npm run db:create 시에 발생하는 decorating 오류

0

233

2

RxJS 디버깅 질문 있습니다.

0

190

3

CacheManager에 대해 질문 있습니다.

0

175

2

로깅은 어떻게 하는게 효율적일까요?

0

225

1

CORS 질문 있습니다.

0

418

2

쿠키 옵션에 대해서 질문 있습니다.

0

184

2

로그아웃 요청이 403 forbidden 에러가 나는데 왜그런걸까요??

0

450

1

401 unauthorized문제

0

289

1

가드의 장점에 대해서 질문이 있습니다.

0

225

1

로그 관리에 대해 질문 있습니다.

0

252

2

CORS 에러 질문 있습니다.

0

319

2

배포 환경 DB 연결 질문 있습니다.

0

411

2

socket io 미 연결 문제 (nest & flutter)

1

1156

3

no elements in sequence 에러 관해서 질문이 있습니다.

0

454

1