zerocho Slack 클론 코딩 (10) - axios로 요청 보내기 CORS, proxy
저번 포스팅에서는 커스텀 훅을 만들어 보았고, 이번 포스팅에서는 axois 요청 및 CORS, proxy에 대해 공부한다.
현재는 리덕스를 사용하지 않기때문에 비동기 요청 로직을 굳이 컴포넌트 밖으로 빼낼 필요가 없다.
회원가입 페이지라면 회원의 정보를 넘기는 비동기 요청을 그냥 회원가입 컴포넌트 내에 작성하는 것이 더 깔끔할 수 있다.
1. axios 설치하기 및 설정
npm i axios
axios를 현재 작업중인 프론트 코드에 설치해 준다.
alecture/signUp/index.tsx 에 axios 를 import 시켜준다.
//axios 추가
import axios from 'axios';
프론트엔드 개발자는 백엔드 개발자에게 다음과 같은 API 명세를 받게 된다.
회사에선 이거보다 자세하게 쓰여있다.
https://github.com/ZeroCho/sleact/blob/master/API.md
위 링크 중 회원가입 요청은 POST /users이다.
아래 사진과 같이 body에는 이메일, 닉네임, 비밀번호를 보낸다
성공하면 : 'ok'로 응답이 온다.
2. axios post api 작성
alecture/signUp/index.tsx 에 onSubmit 로직에 axios를 보내는 로직을 추가해준다.
localhost:3095는 현재 세팅되어있는 백엔드 포트이다.
const onSubmit = useCallback((e) => {
e.preventDefault();
if(mismatchError === false) {
console.log(email,email, nickname, password, passwordCheck )
console.log('서버로 회원가입하기');
//axios 작성
axios.post('http://localhost:3095/api/users' , {
email,
nickname,
password,
})
//성공시 실행
.then((response) => {
console.log(response)
})
//실패시 실행
.catch((error) => {
console.log(error.response)
})
//성공하던 실패하던 공통으로 실행
.finally(() => {})
}
},[email, nickname, password, passwordCheck, mismatchError]);
요청을 보내려면 당연 백엔드는 켜져 있는 상태여야 한다.
프론트와 백엔드를 실행하고 회원가입을 작성하고, 회원가입 버튼을 누른다.
[개발자 도구(F12)] - Network 탭을 보면 다음과 같이 200번대로 회원가입을 성공한 것을 알 수 있다.
그리고 요청이 두 번 가는 것을 확인할 수 있다. (OTIONS) 요청
백엔드와 프런트의 주소가 다르면 CORS 설정을 따로 해줘야 에러가 일어나지 않는다.
3. CORS 설정
back/app.js 파일을 보면 다음과 같은 cors 관련 설정이 있다. 이 코드를 주석으로 하면 cors 에러가 나타나게 된다.
app.use(
cors({
origin: true,
credentials: true,
})
);
다음과 같은 CORS 에러 내용을 보게 될 것이다.
이는 프론트엔드 개발자라면 누구나 다 보는 에러이다.
지금은 백엔드에 위와 같은 설정이 되어 있어 에러가 뜨지 않는 경우이고, 지금은 그 코드를 주석으로 막았으니 에러가 뜨게 된다.
해결 방법은
1. 백엔드 개발자에게 해결해 달라고 한다.
2. 프론트엔드 개발자가 해결하는 방법이 있다.
2. 프론트에서 해결하는 방법을 알아보면
webpack.config.ts 파일의 devServer 부분에 proxy 설정을 추가해주면 된다.
devServer: {
historyApiFallback: true, // react router
port: 3090,
devMiddleware: { publicPath: '/dist/' },
static: { directory: path.resolve(__dirname) },
proxy: {
'/api/': {
target: 'http://localhost:3095',
changeOrigin: true,
},
},
},
위의 proxy 부분은 프론트엔드에서 'api'로 전달하는 요청들은 주소를 3095 포트로 바꿔서 보낸다는 뜻이다.
위의 설정 후 회원가입 컴포넌트 signUp/index.tsx 파일로 오면
const onSubmit = useCallback((e) => {
e.preventDefault();
if(mismatchError === false) {
console.log(email,email, nickname, password, passwordCheck )
console.log('서버로 회원가입하기');
//axios 작성
//url 앞부분을 지운다.
axios.post('/api/users' , {
email,
nickname,
password,
})
//성공시 실행
.then((response) => {
console.log(response)
})
//실패시 실행
.catch((error) => {
console.log(error.response)
})
//성공하던 실패하던 공통으로 실행
.finally(() => {})
}
},[email, nickname, password, passwordCheck, mismatchError]);
onSubmit 함수 내의 axios url 부분을 제거하면 자동으로 3095 포트로 바꿔서 전달되게 된다.
앞에 http://localhost:3095/api/users 라고 입력하게 될 경우는 3090(프론트)에서 3095(백엔드)로 요청하게 되는 것이고,
앞을 생략하여 /api/users 라고 보내게 되면 3095에서 3095(백엔드)로 보내게 되는 차이가 있다.
※ 만약 설정 파일을 바꾸면 프론트엔드를 한번 껐다 켜야 적용이 된다.
바꾼 설정으로 회원가입을 눌러보면 CORS 에러가 사라진 것을 확인할 수 있다.
둘 다 localhost 일 때만 가능하다.
4. 서버에서 보내준 에러 메시지 표시하기
서버에서 보내준 에러 메시지를 표시하기 위해 다음과 같이 변경한다
aleacture/signUp/index.tsx
state에 signUpError를 추가한다.
//서버에서 보내준 에러메세지 표시
const [signUpError, setSignUpError ] = useState(false);
onSubmit() 함수에서 axios. catch 부분에 에러가 발생하면 setSignUpError의 상태를 true로 바꿔준다.
const onSubmit = useCallback((e) => {
e.preventDefault();
if(mismatchError === false) {
console.log(email,email, nickname, password, passwordCheck )
console.log('서버로 회원가입하기');
//axios 작성
axios.post('/api/users' , {
email,
nickname,
password,
})
//성공시 실행
.then((response) => {
console.log(response)
})
//실패시 실행
.catch((error) => {
console.log(error.response)
//에러일때 true
setSignUpError(true);
})
//성공하던 실패하던 공통으로 실행
.finally(() => {})
}
},[email, nickname, password, passwordCheck, mismatchError]);
아래 비밀번호가 일치하지 않습니다. 등 경고 문구를 표시하는 부분에 singUpError를 추가한다.
{mismatchError && <Error>비밀번호가 일치하지 않습니다.</Error>}
{!nickname && <Error>닉네임을 입력해주세요.</Error>}
{/* 에러메세지 */}
{signUpError && <Error>이미 가입된 이메일입니다.</Error>}
회원가입 시 에러가 나면 아래와 같이 경고 문구가 보이게 된다.
하지만 지금은 고정 문자열이기 때문에 다른 에러일 때도 받아야 하니 경고 문구를 보여주는 부분을 변수 처리로 변경한다.
{/* 에러메세지 */}
{signUpError && <Error>{signUpError}</Error>}
아까 설정한 state를 빈 문자열로 초기값을 변경한다.
//서버에서 보내준 에러메세지 표시
const [signUpError, setSignUpError ] = useState('');
onsubmit setSignUpError 부분을 error.response.data로 변경한다.
const onSubmit = useCallback((e) => {
e.preventDefault();
if(mismatchError === false) {
console.log(email,email, nickname, password, passwordCheck )
console.log('서버로 회원가입하기');
//axios 작성
axios.post('/api/users' , {
email,
nickname,
password,
})
//성공시 실행
.then((response) => {
console.log(response)
})
//실패시 실행
.catch((error) => {
console.log(error.response)
//에러일때 true
setSignUpError(error.response.data);
})
//성공하던 실패하던 공통으로 실행
.finally(() => {})
}
},[email, nickname, password, passwordCheck, mismatchError]);
실패에도 응답이 담겨있기 때문에 문자열을 그대로 표시하게 된다.
※ 비 동기 요청 시에. then(). catch(). finally() 다음과 같은 내용 안에서 setState를 하게 될 경우는 axios 요청 전에 초기 화를 해주는 것이 좋다.
왜냐하면 요청을 연달아 날릴 경우 첫 번째 요청에 남아있던 결과가 두 번째 요청에도 똑같이 표시되는 문제가 생긴다.
//요청 전 초기화
setSignUpError('');
//axios 작성
axios.post('/api/users' , {
email,
nickname,
password,
})
//성공시 실행
.then((response) => {
console.log(response)
})
//실패시 실행
.catch((error) => {
console.log(error.response)
//에러일때 true
setSignUpError(error.response.data);
})
//성공하던 실패하던 공통으로 실행
.finally(() => {})
5. 성공 시 로직 작성
aleacture/signUp/index.tsx
signUpSuccess state를 추가해준다.
//서버에서 성공할 경우
const [signUpSuccess, setSignUpSuccess] = useState(false);
onSubmit 함수를 다음과 같이 변경한다.
const onSubmit = useCallback((e) => {
e.preventDefault();
if(mismatchError === false) {
console.log(email,email, nickname, password, passwordCheck )
console.log('서버로 회원가입하기');
//요청 전 초기화
setSignUpError('');
setSignUpSuccess(false);
//axios 작성
axios.post('/api/users' , {
email,
nickname,
password,
})
//성공시 실행
.then((response) => {
console.log(response);
//성공시 signUpSuccess를 true로 바꿔준다.
setSignUpSuccess(true);
})
//실패시 실행
.catch((error) => {
console.log(error.response)
//에러일때 true
setSignUpError(error.response.data);
})
//성공하던 실패하던 공통으로 실행
.finally(() => {})
}
},[email, nickname, password, passwordCheck, mismatchError]);
메시지 부분도 추가해준다.
{signUpSuccess && <Success>회원가입되었습니다! 로그인해주세요.</Success>}
전체 코드
aleacture/signUp/index.tsx
import React, {useCallback, useState} from "react";
import { Success , Header, Form, Label, Input, Button, LinkContainer, Error } from './styles';
import { Link } from 'react-router-dom';
import useInput from '@hooks/useInput';
//axios 추가
import axios from 'axios';
const SignUp = () => {
//단순한 input 중복
const [email, onChangeEmail] = useInput('');
const [nickname,onChangeNickname] = useInput('');
//커스터 마이징이 필요할 때 가운데를 빈 값으로 두면된다.
const [password, ,setPassword] = useInput('');
const [passwordCheck, ,setPasswordCheck] = useInput('');
//비밀번호 , 비밀번호확인 같은지 여부 판단
const [mismatchError, setMissmatchError] = useState(false);
//서버에서 보내준 에러메세지 표시
const [signUpError, setSignUpError ] = useState('');
//서버에서 성공할 경우
const [signUpSuccess, setSignUpSuccess] = useState(false);
//비밀번호 변경 함수
const onChangePassword = useCallback((e) => {
setPassword(e.target.value);
setMissmatchError(e.target.value !== passwordCheck)
},[passwordCheck]);
//비밀번호 확인 변경 함수
const onChangePasswordCheck = useCallback((e) => {
setPasswordCheck(e.target.value);
setMissmatchError(e.target.value !== password)
},[password]);
const onSubmit = useCallback((e) => {
e.preventDefault();
if(mismatchError === false) {
console.log(email,email, nickname, password, passwordCheck )
console.log('서버로 회원가입하기');
//요청 전 초기화
setSignUpError('');
setSignUpSuccess(false);
//axios 작성
axios.post('/api/users' , {
email,
nickname,
password,
})
//성공시 실행
.then((response) => {
console.log(response);
//성공시 signUpSuccess를 true로 바꿔준다.
setSignUpSuccess(true);
})
//실패시 실행
.catch((error) => {
console.log(error.response)
//에러일때 true
setSignUpError(error.response.data);
})
//성공하던 실패하던 공통으로 실행
.finally(() => {})
}
},[email, nickname, password, passwordCheck, mismatchError]);
return (
<div id="container">
<Header>Sleact</Header>
<Form onSubmit={onSubmit}>
<Label id="email-label">
<span>이메일 주소</span>
<div>
<Input type="email" id="email" name="email" value={email} onChange={onChangeEmail} />
</div>
</Label>
<Label id="nickname-label">
<span>닉네임</span>
<div>
<Input type="text" id="nickname" name="nickname" value={nickname} onChange={onChangeNickname} />
</div>
</Label>
<Label id="password-label">
<span>비밀번호</span>
<div>
<Input type="password" id="password" name="password" value={password} onChange={onChangePassword} />
</div>
</Label>
<Label id="password-check-label">
<span>비밀번호 확인</span>
<div>
<Input
type="password"
id="password-check"
name="password-check"
value={passwordCheck}
onChange={onChangePasswordCheck}
/>
</div>
{mismatchError && <Error>비밀번호가 일치하지 않습니다.</Error>}
{!nickname && <Error>닉네임을 입력해주세요.</Error>}
{/* 에러메세지 */}
{signUpError && <Error>{signUpError}</Error>}
{signUpSuccess && <Success>회원가입되었습니다! 로그인해주세요.</Success>}
</Label>
<Button type="submit">회원가입</Button>
</Form>
<LinkContainer>
이미 회원이신가요?
<Link to="/login">로그인 하러가기</Link>
</LinkContainer>
</div>
);
}
export default SignUp;
회원가입이 성공적으로 완료되면 아래처럼 "회원 가입되었습니다.!" 문구가 보인다.
출처 : 슬랙 클론 코딩 zerocho
https://www.inflearn.com/course/클론코딩-실시간채팅
'개발 공부 시리즈 > Slack클론코딩' 카테고리의 다른 글
[인프런 강의] zerocho Slack 클론 코딩 (11) - 로그인 페이지 만들기 (0) | 2022.12.09 |
---|---|
[인프런 강의] zerocho Slack 클론 코딩 (9) - 커스텀 훅 만들기 (1) | 2022.10.26 |
[인프런 강의] zerocho Slack 클론 코딩 (8) - 회원가입 페이지 만들기 (2) | 2022.10.24 |
[인프런 강의] zerocho Slack 클론 코딩 (7) - 코드 스플리팅과 이모션 (0) | 2022.10.20 |
[인프런 강의] zerocho Slack 클론 코딩 (6) - 폴더 구조와 리액트 라우터 (0) | 2022.10.19 |
댓글