이제 회원가입 UI를 생성하고, 기능도 추가해보자.스타일링은 간단하게 레이아웃만 생성해주고, 추후에 모든 UI 생성후 Tailwind CSS를 통해서 스타일링 해주자.
pages
에 regirster.tsx
를 생성하자.
✅ form 태그
안에는 input태그
와 button태그
가 존재.
✅ input태그
를 재사용할 수 있도록 별도의 컴포넌트로 생성하여 InputGroup 컴포넌트
를 import
✅ 만약 회원가입이 되어있다면 로그인 페이지로 이동할 수 있도록 Link태그
사용
const register = () => {
return (
<div>
<div>
<div>
<h1 >회원가입</h1>
<form onSubmit={handleSubmit}>
<InputGroup
placeholder="Email"
value={email}
setValue={setEmail}
error={errors.email}
/>
<InputGroup
placeholder="Username"
value={username}
setValue={setUsername}
error={errors.username}
/>
<InputGroup
placeholder="Password"
value={password}
setValue={setPassword}
error={errors.password}
/>
<button> Sign Up</button>
</form>
<small> 이미 가입하셨나요?<Link href="/login">로그인 </Link> </small>
</div>
</div>
</div>
);
};
export default register;
✅ 전달받은 props에 대한 type을 Interface에서 정의
placeholder
: Email, Username, Passwordvalue
: 각각 input에 입력한 값setValue
: value값을 변경하는 기능✅ cls를 이용하여 error시에 다음과 실행
className={cls({ 'border-red-500': error } )}
...
<small>{error}</small>
💡 잠깐) cls
: classNames를 조건부로 결합하기 위한 간단한 JavaScript 유틸리티.npm install classnames —save
interface InputGroupProps {
placeholder?: string;
value: string;
error: string | undefined;
setValue: (str: string) => void;
}
const InputGroup: React.FC<InputGroupProps> = ({
placeholder = '',
error,
value,
setValue,
}) => {
return (
<div>
<input
type='text'
style={{ minWidth: 300 }}
className={cls({ 'border-red-500': error } )}
placeholder={placeholder}
value={value}
onChange={e => setValue(e.target.value)}
/>
<small>{error}</small>
</div>
);
};
export default InputGroup;
✅ State 생성
const [email, setEmail] = useState('');
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState<any>({});
✅ axios 설치
HTTP 비동기 통신 라이브러리
npm install axios —save
✅ form태그의 submit으로 실행되는 handleSubmit 생성
1️⃣ 백엔드에 회원가입을 위한 요청 (email, password, username을 post)
2️⃣ 회원가입 후 로그인 페이지 자동 이동
3️⃣ error시 setErrors를 통해 reponse값 저장.(에러에 대한 문구)
const router = useRouter();
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
try {
const res = await Axios.post('/auth/register', {
email,
password,
username,
});
console.log('res:', res);
router.push('/login');
} catch (error: any) {
console.log('error:', error);
setErrors(error?.response?.data || {});
}
};
✅ 상위파일에서 axios를 이용해서 요청을 보내는 BaseURL을 글로벌하게 지정
// _app.tsx
export default function App({ Component, pageProps }: AppProps) {
Axios.defaults.baseURL = process.env.NEXT_PUBLIC_SERVER_BASE_URL + '/api';
return <Component {...pageProps} />;
}
// .env
NEXT_PUBLIC_SERVER_BASE_URL= 백엔드url
지금까지 본 것은 client
에서 입력한 회원정보에 대해서 server
로 데이터를 post하고 요청하는 것이었다. 따라서, 그에 맞는 api를 생성하여 response를 보내주자.
✅ routes폴더 안에 auth.ts 생성
src/routes/auth.ts
const register = async (req: Request, res: Response) => {
const { email, username, password } = req.body;
console.log(email, username, password);
}
const router = Router();
router.post('/register', register);
export default router;
✅ Entry파일에 auth route import
src/server.ts
...
app.use('/api/auth', authRoutes);
...
✅ CORS 에러 처리
import cors from 'cors';
const origin = 'http://localhost:3000';
app.use(cors({ origin }));
1️⃣ 이메일과 유저이름을 이미 저장하여 사용되고 있는지를 확인.
2️⃣ 이미 있다면 errors 객체에 넣어준다.
3️⃣ error가 존재한다면 return 값으로 error를 response로 보내준다.
4️⃣ user 정보와 user 인스턴스 생성.
5️⃣ Entity에 정해 놓은 조건으로 user 데이터의 유효성 검사 실시
( class-validator: 데코레이터 및 비데코레이터 기반으로 유효성 검사를 할 수 있다.)
6️⃣ 유저 정보를 user table에 저장.
7️⃣ 저장된 유저정보를 response로 전달
8️⃣ 에러를 response로 전달.
const register = async (req: Request, res: Response) => {
const { email, username, password } = req.body;
try {
let errors: any = {};
const emailUser = await User.findOneBy({ email }); // 1️⃣ 번
const usernameUser = await User.findOneBy({ username }); // 1️⃣ 번
if (emailUser) errors.email = '이미 등록된 이메일 주소입니다.'; // 2️⃣ 번
if (usernameUser) errors.username = '이미 등록된 사용자 이름입니다.'; // 2️⃣ 번
if (Object.keys(errors).length > 0) { //3️⃣ 번
return res.status(400).json(errors);
}
const user = new User(); // 4️⃣ 번
user.email = email;
user.username = username;
user.password = password;
errors = await validate(user); // 5️⃣ 번
await user.save(); // 6️⃣ 번
return res.json(user); // 7️⃣ 번
} catch (error) {
console.log('error', error); // 8️⃣ 번
return res.status(500).json({ error });
}
};
✅ errors
는 entity
에서 설정했던 error
에 대한 값들일 것이다.
✅ 그리고 앞서 보았듯 validate check(유효성 검사)
를 해주었고, errors
가 배열 형태로 있는 것을 확인 할 수 있다.
errors = await validate(user);
따라서 mapErrors
메서드를 통하여 errors
를 인자로 받고, reduce와 Object.entries
를 통해서 배열의 각 error
값을 prev객체
에 전달한다.
const mapErrors = (errors: any[]) => {
return errors.reduce((prev: any, err: any) => {
prev[err.property] = Object.entries(err.constraints)[0][1];
return prev;
}, {});
};
...
// error가 존재한다면 errors
if (errors.length > 0) return res.status(400).json(mapErrors(errors));
객체의 값을 확인해보자.
console.log(Object.entries(err.constraint));
console.log(prev)