모두의 이력서

[모두의 이력서_14일차] Next.js TS에서 로그인 구현 (cors + react-query + axios + recoil)

개발조각 2023. 4. 10. 16:48
728x90
반응형

이제 로그인 페이지 관련 css를 다 만들었으니 이전에 백에서 만든 로그인 라우터로 로그인 기능을 구현해 보겠습니다.

 

cors 해결하기


클라이언트에서는 http://localhost:3000, 서버는 http://localhostL:5000이라 하면

클라이언트에서 axios.get('http://localhost:8000')하게되면 서로 다른 origin으로 인해 CORS 이슈가 생깁니다.

 

저는 CORS 미들웨어를 사용하여 CORS를 해결했습니다.

(이게 가장 쉬운 방법입니다!)

 

1. cors 설치하기

npm i cors

 

2. express 앱에 적용하기

import express from "express";
import cors from 'cors';

const app = express();
app.use(cors()); //모든 접근 허용

import 해오고 설정해 주시면 됩니다.

 

axios 세팅하기


로그인 기능을 구현하기 위해 서버와 통신을 해야 됩니다.

서버와 통신하는 방법으로 Axios라이브러리를 사용했습니다.

 

Aiox 특징

  • 운영 환경에 따라 브라우저의 XMLHttpRequest 객체 또는 Node.js의 http api 사용
  • Promise(ES6) API tkdyd
  • 요청과 응답 데이터의 변형
  • HTTP 요청 취소
  • HTTP 요청과 응답을 JSON 형태도 자동 변경

 

1. axios 설치

npm install axios --save

 

2. axios 세팅

intstance를 설정해 주고 설정해 준 값을 가지고 미리 uri를 작성하려고 합니다.

api
|- index.ts // 인스턴스를 만들고, 인스턴스를 내보냄
|- user.ts // 인스턴스를 import해서, api를 호출하는 함수를 모아 높는 파일(여기서는 /users인 api만 작성 예정)

 

index.ts는 아래 블로그를 참고해서 작성했습니다.

https://pinokio0702.tistory.com/373

 

[Axios][업무][베트남🇻🇳] - Axios instance 생성하고 api 요청 함수 작성하는 방법

안녕하세요. 회사에서 베트남 시니어 개발자 코드를 통해 학습한 내용을 일부 기록한 글입니다. axios를 잘 정리해서 사용한 것 같아서 따라서 사용하고 있습니다. 이 코드를 보고 개발하는 프로

pinokio0702.tistory.com

// index.ts
// axios 인스텐스를 만들고, 인스턴스를 내보냄
import axios from "axios";
const instance = axios.create({
  baseURL: "http://localhost:5000/api",
});

// 요청 타임아웃 설정
instance.defaults.timeout = 2500;

// 요청 인터셉터 추가
instance.interceptors.request.use(
  (config) => {
    // 요청을 보내기 전에 수행할 로직
    return config;
  },
  (error) => {
    // 요청 에러가 발생했을 때 수행할 로직
    console.log(error); // 디버깅
    return Promise.reject(error);
  }
);

// 응답 인터셉터 추가
instance.interceptors.response.use(
  (response) => {
    // 응답에 대한 로직 작성
    const res = response.data;
    return res;
  },
  (error) => {
    // 응답에 에러가 발생했을 때 수행할 로직
    console.log(error); // 디버깅
    return Promise.reject(error);
  }
);

export default instance;

 

3. user.ts 

백에서 로그인 라우터를 만든 거 토대로 user.ts에 API를 만들었습니다.

// 인스턴스를 import해서 api를 호출하는 함수를 모아 높는 파일
import instance from ".";

export const login = async ({
  email,
  password,
}: {
  email: string;
  password: string;
}) => {
  const result = await instance.post("/users/login", {
    email,
    password,
  });

  return result;
};

타입은 나중에... 정리할 예정입니다.

 

 

react-query 기본세팅 및 사용하기


react-query는 서버의 값을 클라이언트에 가져오거나, 캐싱, 값 업데이트, 에러핸들링 등 비동기 과정을 더욱 편하게 하는 데 사용됩니다.

 

1. react-query 설치하기

타입스크립트 사용하시면 아래와 같이 설치해 주어야 됩니다.

react-query 설치

npm i @tanstack/react-query

 

devtools 설치

npm i -D @tanstack/react-query-devtools

 

2. _app.tsx에 설정해 줍니다.

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const _app = ({ Component }: AppProps) => {
  return (
    <>
          <QueryClientProvider client={queryClient}>
            <ReactQueryDevtools initialIsOpen={false} />
            <Component />
          </QueryClientProvider>
    </>
  );
};

export default _app;

 

3. react-query mutation 사용하기

 

mutation 설정

여기서 login은 axios에서 만든 api입니다.

  const mutation = useMutation({
    mutationFn: login,
    onSuccess: (data) => {
      console.log(data);
    },
  });

 

submit에 넣기

mutation.mutate({ email: email, password: password });

 

Login.tsx

import { useMutation } from "@tanstack/react-query";

import { validateEmail, validatePassword } from "../utils/regExp";
import { login } from "../api/user";

const Login = () => {
  const [warningMsg, setWarningMsg] = useState(false);

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const mutation = useMutation({
    mutationFn: login,
    onSuccess: (data) => {
      console.log(data);
    },
  });

  const onEmailHandler = (e: ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target as any;
    setEmail(value);
  };

  const onPasswordHandler = (e: ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target as any;
    setPassword(value);
  };

  const onSubmitHandler = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault(); // 리프레시 막기

    if (
      !email ||
      !password ||
      !validateEmail(email) ||
      !validatePassword(password)
    ) {
      setWarningMsg(true);
    }

    mutation.mutate({ email: email, password: password });
  };

  return (
  ... 생략
  );
};

export default Login;

 

이러고 알맞게 로그인하면 cosole.log(data)에는 아래와 같은 결과가 나옵니다.

이제 유저의 nickName을 로컬에 넣고 리코일에다가도 넣어 로그인 관리를 하겠습니다.

 

recoil 세팅 및 사용하기


1. recoil 설치하기

npm install recoil

 

2. _app.tsx에 리코일 설정하기

import { RecoilRoot } from "recoil";

return(
	<RecoilRoot>
		<Component />
	</RecoilRoot>
)

 

3. recoil-persist 설치

localstorage에 넣어줘서 구현해도 되지만 조금 더 편하게 관리하기 위해 recoil-persist설치하겠습니다.

recoil-persist
recoil persist 영구적으로 사용자가 캐시를 삭제할 때까지 영구적으로 state 관리

 

쉽게 설명하자면 로그인 성공 시 아래와 같은 효과를 만들기 위해 사용할 예정입니다.

 

 

4. 리코일 파일 만들고 설정하기

front
	|- recoil
		|- user.ts

 

user.ts

import { atom } from "recoil";
import { recoilPersist } from "recoil-persist";

const { persistAtom } = recoilPersist(); // 페이지가 변경되더라도 상태관리를 유지하기 위해 사용된다.

interface User {
  id: string;
  nickName: string;
}

export const userAtom = atom<User | null>({
  key: "USER_DATA",
  default: null,
  effects_UNSTABLE: [persistAtom],
});

 

5. 로그인 설정

import { useSetRecoilState } from "recoil";

const setUser = useSetRecoilState(userAtom);

recoil의 useSetRecoilState를 사용하시면

const [test, setTest] = useState()에서 setTest와 같은 효과가 나타납니다.

 

import { useRouter } from "next/router";

const Login = () => {
  const router = useRouter();

  const mutation = useMutation({
    mutationFn: login,
    onSuccess: (data) => {
      setUser(data.data);
      router.replace("/");
    },
  });
 }

 

 

이제 react-query와 같이 사용하여 로그인을 성공할 경우  setUser에 넣어주고 싶은 데이터를 넣어주시면 됩니다.

마지막으로 로그인하면 메인페이지로 돌아갈 수 있도록 하기 위해 router.replace("/");를 사용해 주었습니다.

push로 이동시키면 history stack에 쌓여서 뒤로가기가 가능하고
replace로 이동시키면 history stack에 안쌓여서 뒤로가기 불가능

 

최종코드

// Login.tsx
import { ChangeEvent, FormEvent, useContext, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useMutation } from "@tanstack/react-query";
import { useSetRecoilState } from "recoil";

import Logo from "../components/common/Logo";
import { IsDark } from "../context/type";
import { ThemeContext } from "../context/themeContext";
import { validateEmail, validatePassword } from "../utils/regExp";
import { login } from "../api/user";
import { userAtom } from "../recoil/user";

import * as styled from "../styles/Login";

const Login = () => {
  const setUser = useSetRecoilState(userAtom);

  const router = useRouter();

  const { isDark } = useContext(ThemeContext) as IsDark;
  const mode = isDark ? "darkTheme" : "lightTheme";

  const [warningMsg, setWarningMsg] = useState(false);

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const mutation = useMutation({
    mutationFn: login,
    onSuccess: (data) => {
      setUser(data.data);
      router.replace("/");
    },
  });

  const onEmailHandler = (e: ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target as any;
    setEmail(value);
  };

  const onPasswordHandler = (e: ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target as any;
    setPassword(value);
  };

  const onSubmitHandler = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault(); // 리프레시 막기

    if (
      !email ||
      !password ||
      !validateEmail(email) ||
      !validatePassword(password)
    ) {
      setWarningMsg(true);
    }

    mutation.mutate({ email: email, password: password });
  };

  return (
    <styled.LoginWrap mode={mode}>
      <styled.LoginCon mode={mode}>
        <Logo />
        <styled.LoginformWrap>
          <form onSubmit={onSubmitHandler}>
            <styled.InputBox mode={mode}>
              <p>이메일</p>
              <input
                autoFocus
                type="text"
                value={email}
                onChange={onEmailHandler}
              />
            </styled.InputBox>

            <styled.InputBox mode={mode}>
              <p>비밀번호</p>
              <input type="password" onChange={onPasswordHandler} />
            </styled.InputBox>

            {warningMsg && (
              <styled.WarningMsg>
                가입되어 있지 않은 계정이거나,
                <br />
                이메일 또는 비밀번호가 일치하지 않습니다.
              </styled.WarningMsg>
            )}

            <styled.btn type="submit">로그인</styled.btn>
          </form>
        </styled.LoginformWrap>
        <styled.RegisterBox>
          <Link href="/Register" as="/auth/register">
            <span>아직도 회원이 아니세요?</span>
            <span>회원가입 하기</span>
          </Link>
        </styled.RegisterBox>
      </styled.LoginCon>
    </styled.LoginWrap>
  );
};

export default Login;

 

로그인 성공-!

 

참고자료


https://velog.io/@wiostz98kr/React-Express-CORS-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0

 

React + Express | CORS 설정하기

CORS개념은 이전 포스팅에서 다뤘으므로 생략하겠습니다. 프론트는 http://localhost:3000, 서버는 http://localhost:8000이라고 가정할 때, 프론트에서 axios.get('http://localhost:8000')하게 되면 서로 다른 o

velog.io

https://pinokio0702.tistory.com/373

 

[Axios][업무][베트남🇻🇳] - Axios instance 생성하고 api 요청 함수 작성하는 방법

안녕하세요. 회사에서 베트남 시니어 개발자 코드를 통해 학습한 내용을 일부 기록한 글입니다. axios를 잘 정리해서 사용한 것 같아서 따라서 사용하고 있습니다. 이 코드를 보고 개발하는 프로

pinokio0702.tistory.com

https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-%EC%84%A4%EC%B9%98-%EC%82%AC%EC%9A%A9

 

📚 AXIOS 설치 & 특징 & 문법 💯 정리

Axios 라이브러리 Axios는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리 아다. 쉽게 말해서 백엔드랑 프론트엔드랑 통신을 쉽게하기 위해 Ajax와 더불어 사용한다. 이미

inpa.tistory.com

https://velog.io/@gkj8963/React-Query

 

React Query(TypeScript + React v18)

0 . installnpm i react-queryimportindex.jsimport { QueryClient, QueryClientProvider } from "react-query";queryClient 생성 후 provider에 넣어주기fetcher 함수 만들기

velog.io

https://kyounghwan01.github.io/blog/React/react-query/basic/#%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%82%E1%85%B3%E1%86%AB-%E1%84%8B%E1%85%B5%E1%84%8B%E1%85%B2

 

react-query 개념 및 정리

react-query 개념 및 정리, react, react16, hook, useState, useRef, useMemo, useEffect, useReducer, useCallback, useQuery 동기적으로 실행

kyounghwan01.github.io

https://velog.io/@tamagoyakii/42byte-Recoil%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0

 

[42byte] Recoil로 로그인 상태 관리하기

42byte는 42서울 학생들만 이용할 수 있는 커뮤니티다. 따라서 OAuth 프로토콜을 이용한 Access Token이 필요하다. 서버에서 url에 담아서 보내준 Access Token으로 로그인을 정보를 관리하는 방법에 대해

velog.io

https://im-designloper.tistory.com/102

 

[Next.js] next/router 사용하기 (공식문서 내용 정리)

next/router 사용하기 router 객체에 접근하기 위해서 useRouter를 사용한다 1. import 후 import { useRouter } from 'next/router' 2. 아래의 형태로 사용 const router = useRouter() 아래처럼 console에서 router 객체에 대한 정

im-designloper.tistory.com

https://www.youtube.com/watch?v=joMOF30x_NY 

https://www.youtube.com/watch?v=yAodvlX7oug 

 

728x90
반응형